@opentermsarchive/engine 2.3.0 → 2.3.2
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.
- package/package.json +2 -2
- package/src/reporter/github.js +96 -130
- package/src/reporter/github.test.js +10 -88
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opentermsarchive/engine",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.2",
|
|
4
4
|
"description": "Tracks and makes visible changes to the terms of online services",
|
|
5
5
|
"homepage": "https://opentermsarchive.org",
|
|
6
6
|
"bugs": {
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
"mongodb": "^4.9.0",
|
|
91
91
|
"morgan": "^1.10.0",
|
|
92
92
|
"node-fetch": "^3.1.0",
|
|
93
|
-
"octokit": "
|
|
93
|
+
"octokit": "2.0.2",
|
|
94
94
|
"pdfjs-dist": "^2.9.359",
|
|
95
95
|
"puppeteer": "^22.8.1",
|
|
96
96
|
"puppeteer-extra": "^3.3.6",
|
package/src/reporter/github.js
CHANGED
|
@@ -16,7 +16,14 @@ export default class GitHub {
|
|
|
16
16
|
constructor(repository) {
|
|
17
17
|
const { version } = require('../../package.json');
|
|
18
18
|
|
|
19
|
-
this.octokit = new Octokit({
|
|
19
|
+
this.octokit = new Octokit({
|
|
20
|
+
auth: process.env.OTA_ENGINE_GITHUB_TOKEN,
|
|
21
|
+
userAgent: `opentermsarchive/${version}`,
|
|
22
|
+
throttle: {
|
|
23
|
+
onRateLimit: () => false, // Do not retry after hitting a rate limit error
|
|
24
|
+
onSecondaryRateLimit: () => false, // Do not retry after hitting a secondary rate limit error
|
|
25
|
+
},
|
|
26
|
+
});
|
|
20
27
|
|
|
21
28
|
const [ owner, repo ] = repository.split('/');
|
|
22
29
|
|
|
@@ -25,173 +32,132 @@ export default class GitHub {
|
|
|
25
32
|
|
|
26
33
|
async initialize() {
|
|
27
34
|
this.MANAGED_LABELS = require('./labels.json');
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
35
|
+
try {
|
|
36
|
+
const existingLabels = await this.getRepositoryLabels();
|
|
37
|
+
const existingLabelsNames = existingLabels.map(label => label.name);
|
|
38
|
+
const missingLabels = this.MANAGED_LABELS.filter(label => !existingLabelsNames.includes(label.name));
|
|
39
|
+
|
|
40
|
+
if (missingLabels.length) {
|
|
41
|
+
logger.info(`Following required labels are not present on the repository: ${missingLabels.map(label => `"${label.name}"`).join(', ')}. Creating them…`);
|
|
42
|
+
|
|
43
|
+
for (const label of missingLabels) {
|
|
44
|
+
await this.createLabel({ /* eslint-disable-line no-await-in-loop */
|
|
45
|
+
name: label.name,
|
|
46
|
+
color: label.color,
|
|
47
|
+
description: `${label.description} ${MANAGED_BY_OTA_MARKER}`,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
42
50
|
}
|
|
51
|
+
} catch (error) {
|
|
52
|
+
logger.error(`Failed to handle repository labels: ${error.message}`);
|
|
43
53
|
}
|
|
44
54
|
}
|
|
45
55
|
|
|
46
56
|
async getRepositoryLabels() {
|
|
47
|
-
|
|
48
|
-
const { data: labels } = await this.octokit.request('GET /repos/{owner}/{repo}/labels', { ...this.commonParams });
|
|
57
|
+
const { data: labels } = await this.octokit.request('GET /repos/{owner}/{repo}/labels', { ...this.commonParams });
|
|
49
58
|
|
|
50
|
-
|
|
51
|
-
} catch (error) {
|
|
52
|
-
logger.error(`🤖 Could not get labels: ${error}`);
|
|
53
|
-
}
|
|
59
|
+
return labels;
|
|
54
60
|
}
|
|
55
61
|
|
|
56
62
|
async createLabel({ name, color, description }) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
logger.info(`🤖 Created repository label "${name}"`);
|
|
66
|
-
} catch (error) {
|
|
67
|
-
logger.error(`🤖 Could not create label "${name}": ${error}`);
|
|
68
|
-
}
|
|
63
|
+
await this.octokit.request('POST /repos/{owner}/{repo}/labels', {
|
|
64
|
+
...this.commonParams,
|
|
65
|
+
name,
|
|
66
|
+
color,
|
|
67
|
+
description,
|
|
68
|
+
});
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
async createIssue({ title, description: body, labels }) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
logger.info(`🤖 Created GitHub issue #${issue.number} "${title}": ${issue.html_url}`);
|
|
81
|
-
|
|
82
|
-
return issue;
|
|
83
|
-
} catch (error) {
|
|
84
|
-
logger.error(`🤖 Could not create GitHub issue "${title}": ${error}`);
|
|
85
|
-
}
|
|
72
|
+
const { data: issue } = await this.octokit.request('POST /repos/{owner}/{repo}/issues', {
|
|
73
|
+
...this.commonParams,
|
|
74
|
+
title,
|
|
75
|
+
body,
|
|
76
|
+
labels,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
return issue;
|
|
86
80
|
}
|
|
87
81
|
|
|
88
|
-
async
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
82
|
+
async updateIssue(issue, { state, labels }) {
|
|
83
|
+
const { data: updatedIssue } = await this.octokit.request('PATCH /repos/{owner}/{repo}/issues/{issue_number}', {
|
|
84
|
+
...this.commonParams,
|
|
85
|
+
issue_number: issue.number,
|
|
86
|
+
state,
|
|
87
|
+
labels,
|
|
88
|
+
});
|
|
95
89
|
|
|
96
|
-
|
|
97
|
-
} catch (error) {
|
|
98
|
-
logger.error(`🤖 Could not update GitHub issue #${issue.number} "${issue.title}": ${error}`);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
async openIssue(issue) {
|
|
103
|
-
try {
|
|
104
|
-
await this.octokit.request('PATCH /repos/{owner}/{repo}/issues/{issue_number}', {
|
|
105
|
-
...this.commonParams,
|
|
106
|
-
issue_number: issue.number,
|
|
107
|
-
state: GitHub.ISSUE_STATE_OPEN,
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
logger.info(`🤖 Opened GitHub issue #${issue.number}`);
|
|
111
|
-
} catch (error) {
|
|
112
|
-
logger.error(`🤖 Could not update GitHub issue #${issue.number} "${issue.title}": ${error}`);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
async closeIssue(issue) {
|
|
117
|
-
try {
|
|
118
|
-
await this.octokit.request('PATCH /repos/{owner}/{repo}/issues/{issue_number}', {
|
|
119
|
-
...this.commonParams,
|
|
120
|
-
issue_number: issue.number,
|
|
121
|
-
state: GitHub.ISSUE_STATE_CLOSED,
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
logger.info(`🤖 Closed GitHub issue #${issue.number}`);
|
|
125
|
-
} catch (error) {
|
|
126
|
-
logger.error(`🤖 Could not update GitHub issue #${issue.number} "${issue.title}": ${error}`);
|
|
127
|
-
}
|
|
90
|
+
return updatedIssue;
|
|
128
91
|
}
|
|
129
92
|
|
|
130
93
|
async getIssue({ title, ...searchParams }) {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}, response => response.data);
|
|
94
|
+
const issues = await this.octokit.paginate('GET /repos/{owner}/{repo}/issues', {
|
|
95
|
+
...this.commonParams,
|
|
96
|
+
per_page: 100,
|
|
97
|
+
...searchParams,
|
|
98
|
+
}, response => response.data);
|
|
137
99
|
|
|
138
|
-
|
|
100
|
+
const [issue] = issues.filter(item => item.title === title); // Since only one is expected, use the first one
|
|
139
101
|
|
|
140
|
-
|
|
141
|
-
} catch (error) {
|
|
142
|
-
logger.error(`🤖 Could not find GitHub issue "${title}": ${error}`);
|
|
143
|
-
}
|
|
102
|
+
return issue;
|
|
144
103
|
}
|
|
145
104
|
|
|
146
105
|
async addCommentToIssue({ issue, comment: body }) {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
});
|
|
106
|
+
const { data: comment } = await this.octokit.request('POST /repos/{owner}/{repo}/issues/{issue_number}/comments', {
|
|
107
|
+
...this.commonParams,
|
|
108
|
+
issue_number: issue.number,
|
|
109
|
+
body,
|
|
110
|
+
});
|
|
153
111
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
return comment;
|
|
157
|
-
} catch (error) {
|
|
158
|
-
logger.error(`🤖 Could not add comment to GitHub issue #${issue.number} "${issue.title}": ${error}`);
|
|
159
|
-
}
|
|
112
|
+
return comment;
|
|
160
113
|
}
|
|
161
114
|
|
|
162
115
|
async closeIssueWithCommentIfExists({ title, comment }) {
|
|
163
|
-
|
|
116
|
+
try {
|
|
117
|
+
const openedIssue = await this.getIssue({ title, state: GitHub.ISSUE_STATE_OPEN });
|
|
164
118
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
119
|
+
if (!openedIssue) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
168
122
|
|
|
169
|
-
|
|
123
|
+
await this.addCommentToIssue({ issue: openedIssue, comment });
|
|
124
|
+
logger.info(`Added comment to issue #${openedIssue.number}: ${openedIssue.html_url}`);
|
|
170
125
|
|
|
171
|
-
|
|
126
|
+
await this.updateIssue(openedIssue, { state: GitHub.ISSUE_STATE_CLOSED });
|
|
127
|
+
logger.info(`Closed issue #${openedIssue.number}: ${openedIssue.html_url}`);
|
|
128
|
+
} catch (error) {
|
|
129
|
+
logger.error(`Failed to update issue "${title}": ${error.message}`);
|
|
130
|
+
}
|
|
172
131
|
}
|
|
173
132
|
|
|
174
133
|
async createOrUpdateIssue({ title, description, label }) {
|
|
175
|
-
|
|
134
|
+
try {
|
|
135
|
+
const issue = await this.getIssue({ title, state: GitHub.ISSUE_STATE_ALL });
|
|
176
136
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
}
|
|
137
|
+
if (!issue) {
|
|
138
|
+
const createdIssue = await this.createIssue({ title, description, labels: [label] });
|
|
180
139
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
}
|
|
140
|
+
return logger.info(`Created issue #${createdIssue.number} "${title}": ${createdIssue.html_url}`);
|
|
141
|
+
}
|
|
184
142
|
|
|
185
|
-
|
|
186
|
-
|
|
143
|
+
const managedLabelsNames = this.MANAGED_LABELS.map(label => label.name);
|
|
144
|
+
const labelsNotManagedToKeep = issue.labels.map(label => label.name).filter(label => !managedLabelsNames.includes(label));
|
|
145
|
+
const [managedLabel] = issue.labels.filter(label => managedLabelsNames.includes(label.name)); // It is assumed that only one specific reason for failure is possible at a time, making managed labels mutually exclusive
|
|
187
146
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
147
|
+
if (issue.state !== GitHub.ISSUE_STATE_CLOSED && managedLabel?.name === label) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
191
150
|
|
|
192
|
-
|
|
151
|
+
await this.updateIssue(issue, {
|
|
152
|
+
state: GitHub.ISSUE_STATE_OPEN,
|
|
153
|
+
labels: [ label, ...labelsNotManagedToKeep ],
|
|
154
|
+
});
|
|
155
|
+
logger.info(`Updated issue #${issue.number}: ${issue.html_url}`);
|
|
156
|
+
await this.addCommentToIssue({ issue, comment: description });
|
|
193
157
|
|
|
194
|
-
|
|
195
|
-
|
|
158
|
+
logger.info(`Added comment to issue #${issue.number}: ${issue.html_url}`);
|
|
159
|
+
} catch (error) {
|
|
160
|
+
logger.error(`Failed to update issue "${title}": ${error.message}`);
|
|
161
|
+
}
|
|
196
162
|
}
|
|
197
163
|
}
|
|
@@ -118,66 +118,6 @@ describe('GitHub', function () {
|
|
|
118
118
|
});
|
|
119
119
|
});
|
|
120
120
|
|
|
121
|
-
describe('#setIssueLabels', () => {
|
|
122
|
-
let scope;
|
|
123
|
-
const ISSUE_NUMBER = 123;
|
|
124
|
-
const LABELS = [ 'bug', 'enhancement' ];
|
|
125
|
-
|
|
126
|
-
before(async () => {
|
|
127
|
-
scope = nock('https://api.github.com')
|
|
128
|
-
.put(`/repos/owner/repo/issues/${ISSUE_NUMBER}/labels`, { labels: LABELS })
|
|
129
|
-
.reply(200);
|
|
130
|
-
|
|
131
|
-
await github.setIssueLabels({ issue: { number: ISSUE_NUMBER }, labels: LABELS });
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
after(nock.cleanAll);
|
|
135
|
-
|
|
136
|
-
it('sets labels on the issue', () => {
|
|
137
|
-
expect(scope.isDone()).to.be.true;
|
|
138
|
-
});
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
describe('#openIssue', () => {
|
|
142
|
-
let scope;
|
|
143
|
-
const ISSUE = { number: 123 };
|
|
144
|
-
const EXPECTED_REQUEST_BODY = { state: 'open' };
|
|
145
|
-
|
|
146
|
-
before(async () => {
|
|
147
|
-
scope = nock('https://api.github.com')
|
|
148
|
-
.patch(`/repos/owner/repo/issues/${ISSUE.number}`, EXPECTED_REQUEST_BODY)
|
|
149
|
-
.reply(200);
|
|
150
|
-
|
|
151
|
-
await github.openIssue(ISSUE);
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
after(nock.cleanAll);
|
|
155
|
-
|
|
156
|
-
it('opens the issue', () => {
|
|
157
|
-
expect(scope.isDone()).to.be.true;
|
|
158
|
-
});
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
describe('#closeIssue', () => {
|
|
162
|
-
let scope;
|
|
163
|
-
const ISSUE = { number: 123 };
|
|
164
|
-
const EXPECTED_REQUEST_BODY = { state: 'closed' };
|
|
165
|
-
|
|
166
|
-
before(async () => {
|
|
167
|
-
scope = nock('https://api.github.com')
|
|
168
|
-
.patch(`/repos/owner/repo/issues/${ISSUE.number}`, EXPECTED_REQUEST_BODY)
|
|
169
|
-
.reply(200);
|
|
170
|
-
|
|
171
|
-
await github.closeIssue(ISSUE);
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
after(nock.cleanAll);
|
|
175
|
-
|
|
176
|
-
it('closes the issue', () => {
|
|
177
|
-
expect(scope.isDone()).to.be.true;
|
|
178
|
-
});
|
|
179
|
-
});
|
|
180
|
-
|
|
181
121
|
describe('#getIssue', () => {
|
|
182
122
|
let scope;
|
|
183
123
|
let result;
|
|
@@ -376,9 +316,8 @@ describe('GitHub', function () {
|
|
|
376
316
|
};
|
|
377
317
|
|
|
378
318
|
context('when issue is closed', () => {
|
|
379
|
-
let
|
|
319
|
+
let updateIssueScope;
|
|
380
320
|
let addCommentScope;
|
|
381
|
-
let openIssueScope;
|
|
382
321
|
|
|
383
322
|
const GITHUB_RESPONSE_FOR_EXISTING_ISSUE = {
|
|
384
323
|
number: 123,
|
|
@@ -394,12 +333,8 @@ describe('GitHub', function () {
|
|
|
394
333
|
.query(true)
|
|
395
334
|
.reply(200, [GITHUB_RESPONSE_FOR_EXISTING_ISSUE]);
|
|
396
335
|
|
|
397
|
-
|
|
398
|
-
.patch(`/repos/owner/repo/issues/${GITHUB_RESPONSE_FOR_EXISTING_ISSUE.number}`, { state: GitHub.ISSUE_STATE_OPEN })
|
|
399
|
-
.reply(200);
|
|
400
|
-
|
|
401
|
-
setIssueLabelsScope = nock('https://api.github.com')
|
|
402
|
-
.put(`/repos/owner/repo/issues/${GITHUB_RESPONSE_FOR_EXISTING_ISSUE.number}/labels`, { labels: ['location'] })
|
|
336
|
+
updateIssueScope = nock('https://api.github.com')
|
|
337
|
+
.patch(`/repos/owner/repo/issues/${GITHUB_RESPONSE_FOR_EXISTING_ISSUE.number}`, { state: GitHub.ISSUE_STATE_OPEN, labels: ['location'] })
|
|
403
338
|
.reply(200);
|
|
404
339
|
|
|
405
340
|
addCommentScope = nock('https://api.github.com')
|
|
@@ -409,12 +344,8 @@ describe('GitHub', function () {
|
|
|
409
344
|
await github.createOrUpdateIssue(ISSUE);
|
|
410
345
|
});
|
|
411
346
|
|
|
412
|
-
it('reopens the issue', () => {
|
|
413
|
-
expect(
|
|
414
|
-
});
|
|
415
|
-
|
|
416
|
-
it("updates the issue's label", () => {
|
|
417
|
-
expect(setIssueLabelsScope.isDone()).to.be.true;
|
|
347
|
+
it('reopens the issue and its labels', () => {
|
|
348
|
+
expect(updateIssueScope.isDone()).to.be.true;
|
|
418
349
|
});
|
|
419
350
|
|
|
420
351
|
it('adds comment to the issue', () => {
|
|
@@ -423,9 +354,8 @@ describe('GitHub', function () {
|
|
|
423
354
|
});
|
|
424
355
|
|
|
425
356
|
context('when issue is already opened', () => {
|
|
426
|
-
let setIssueLabelsScope;
|
|
427
357
|
let addCommentScope;
|
|
428
|
-
let
|
|
358
|
+
let updateIssueScope;
|
|
429
359
|
|
|
430
360
|
const GITHUB_RESPONSE_FOR_EXISTING_ISSUE = {
|
|
431
361
|
number: 123,
|
|
@@ -441,12 +371,8 @@ describe('GitHub', function () {
|
|
|
441
371
|
.query(true)
|
|
442
372
|
.reply(200, [GITHUB_RESPONSE_FOR_EXISTING_ISSUE]);
|
|
443
373
|
|
|
444
|
-
|
|
445
|
-
.patch(`/repos/owner/repo/issues/${GITHUB_RESPONSE_FOR_EXISTING_ISSUE.number}`, { state: GitHub.ISSUE_STATE_OPEN })
|
|
446
|
-
.reply(200);
|
|
447
|
-
|
|
448
|
-
setIssueLabelsScope = nock('https://api.github.com')
|
|
449
|
-
.put(`/repos/owner/repo/issues/${GITHUB_RESPONSE_FOR_EXISTING_ISSUE.number}/labels`, { labels: ['location'] })
|
|
374
|
+
updateIssueScope = nock('https://api.github.com')
|
|
375
|
+
.patch(`/repos/owner/repo/issues/${GITHUB_RESPONSE_FOR_EXISTING_ISSUE.number}`, { state: GitHub.ISSUE_STATE_OPEN, labels: ['location'] })
|
|
450
376
|
.reply(200);
|
|
451
377
|
|
|
452
378
|
addCommentScope = nock('https://api.github.com')
|
|
@@ -456,12 +382,8 @@ describe('GitHub', function () {
|
|
|
456
382
|
await github.createOrUpdateIssue(ISSUE);
|
|
457
383
|
});
|
|
458
384
|
|
|
459
|
-
it(
|
|
460
|
-
expect(
|
|
461
|
-
});
|
|
462
|
-
|
|
463
|
-
it("updates the issue's label", () => {
|
|
464
|
-
expect(setIssueLabelsScope.isDone()).to.be.true;
|
|
385
|
+
it("updates the issue's labels", () => {
|
|
386
|
+
expect(updateIssueScope.isDone()).to.be.true;
|
|
465
387
|
});
|
|
466
388
|
|
|
467
389
|
it('adds comment to the issue', () => {
|