1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | const tslib_1 = require("tslib");
|
4 | const graphql_1 = require("@octokit/graphql");
|
5 | const plugin_enterprise_compatibility_1 = tslib_1.__importDefault(require("@octokit/plugin-enterprise-compatibility"));
|
6 | const path_1 = tslib_1.__importDefault(require("path"));
|
7 | const plugin_retry_1 = tslib_1.__importDefault(require("@octokit/plugin-retry"));
|
8 | const plugin_throttling_1 = tslib_1.__importDefault(require("@octokit/plugin-throttling"));
|
9 | const rest_1 = tslib_1.__importDefault(require("@octokit/rest"));
|
10 | const gitlog_1 = tslib_1.__importDefault(require("gitlog"));
|
11 | const tinycolor2_1 = tslib_1.__importDefault(require("tinycolor2"));
|
12 | const util_1 = require("util");
|
13 | const endent_1 = tslib_1.__importDefault(require("endent"));
|
14 | const typescript_memoize_1 = require("typescript-memoize");
|
15 | const exec_promise_1 = tslib_1.__importDefault(require("./utils/exec-promise"));
|
16 | const logger_1 = require("./utils/logger");
|
17 | const gitlog = util_1.promisify(gitlog_1.default);
|
18 |
|
19 | class GitAPIError extends Error {
|
20 |
|
21 | constructor(api, args, origError) {
|
22 | super(`Error calling github: ${api}\n\twith: ${JSON.stringify(args)}.\n\t${origError.message}`);
|
23 | }
|
24 | }
|
25 |
|
26 | const makeIdentifier = (type, context) => `<!-- GITHUB_RELEASE ${type}: ${context} -->`;
|
27 |
|
28 | const makeCommentIdentifier = (context) => makeIdentifier('COMMENT', context);
|
29 |
|
30 | const makePrBodyIdentifier = (context) => makeIdentifier('PR BODY', context);
|
31 |
|
32 |
|
33 |
|
34 |
|
35 | class Git {
|
36 |
|
37 | constructor(options, logger = logger_1.dummyLog()) {
|
38 | this.logger = logger;
|
39 | this.options = options;
|
40 | this.baseUrl = this.options.baseUrl || 'https://api.github.com';
|
41 | this.graphqlBaseUrl = this.options.graphqlBaseUrl || this.baseUrl;
|
42 | this.logger.veryVerbose.info(`Initializing GitHub with: ${this.baseUrl}`);
|
43 | const GitHub = rest_1.default.plugin(plugin_enterprise_compatibility_1.default)
|
44 | .plugin(plugin_retry_1.default)
|
45 | .plugin(plugin_throttling_1.default);
|
46 | this.github = new GitHub({
|
47 | baseUrl: this.baseUrl,
|
48 | agent: this.options.agent,
|
49 | auth: this.options.token,
|
50 | previews: ['symmetra-preview'],
|
51 | throttle: {
|
52 | onRateLimit: (retryAfter, opts) => {
|
53 | this.logger.log.warn(`Request quota exhausted for request ${opts.method} ${opts.url}`);
|
54 | if (opts.request.retryCount < 5) {
|
55 | this.logger.verbose.log(`Retrying after ${retryAfter} seconds!`);
|
56 | return true;
|
57 | }
|
58 | },
|
59 | onAbuseLimit: (retryAfter, opts) => {
|
60 |
|
61 | this.logger.log.error(`Went over abuse rate limit ${opts.method} ${opts.url}`);
|
62 | }
|
63 | }
|
64 | });
|
65 | this.github.hook.error('request', error => {
|
66 | var _a, _b;
|
67 | if ((_b = (_a = error) === null || _a === void 0 ? void 0 : _a.headers) === null || _b === void 0 ? void 0 : _b.authorization) {
|
68 | delete error.headers.authorization;
|
69 | }
|
70 | throw error;
|
71 | });
|
72 | }
|
73 |
|
74 | async getLatestReleaseInfo() {
|
75 | const latestRelease = await this.github.repos.getLatestRelease({
|
76 | owner: this.options.owner,
|
77 | repo: this.options.repo
|
78 | });
|
79 | return latestRelease.data;
|
80 | }
|
81 |
|
82 | async getLatestRelease() {
|
83 | try {
|
84 | const latestRelease = await this.getLatestReleaseInfo();
|
85 | this.logger.veryVerbose.info('Got response for "getLatestRelease":\n', latestRelease);
|
86 | this.logger.verbose.info('Got latest release:\n', latestRelease);
|
87 | return latestRelease.tag_name;
|
88 | }
|
89 | catch (e) {
|
90 | if (e.status === 404) {
|
91 | this.logger.verbose.info("Couldn't find latest release on GitHub, using first commit.");
|
92 | return this.getFirstCommit();
|
93 | }
|
94 | throw e;
|
95 | }
|
96 | }
|
97 |
|
98 | async getCommitDate(sha) {
|
99 | const date = await exec_promise_1.default('git', ['show', '-s', '--format=%ci', sha]);
|
100 | const [day, time, timezone] = date.split(' ');
|
101 | return `${day}T${time}${timezone}`;
|
102 | }
|
103 |
|
104 | async getFirstCommit() {
|
105 | const list = await exec_promise_1.default('git', ['rev-list', 'HEAD']);
|
106 | return list.split('\n').pop();
|
107 | }
|
108 |
|
109 | async getSha(short) {
|
110 | const result = await exec_promise_1.default('git', [
|
111 | 'rev-parse',
|
112 | short && '--short',
|
113 | 'HEAD'
|
114 | ]);
|
115 | this.logger.verbose.info(`Got commit SHA from HEAD: ${result}`);
|
116 | return result;
|
117 | }
|
118 |
|
119 | async getLabels(prNumber) {
|
120 | this.logger.verbose.info(`Getting labels for PR: ${prNumber}`);
|
121 | const args = {
|
122 | owner: this.options.owner,
|
123 | repo: this.options.repo,
|
124 | issue_number: prNumber
|
125 | };
|
126 | this.logger.verbose.info('Getting issue labels using:', args);
|
127 | try {
|
128 | const labels = await this.github.issues.listLabelsOnIssue(args);
|
129 | this.logger.veryVerbose.info('Got response for "listLabelsOnIssue":\n', labels);
|
130 | this.logger.verbose.info('Found labels on PR:\n', labels.data);
|
131 | return labels.data.map(l => l.name);
|
132 | }
|
133 | catch (e) {
|
134 | throw new GitAPIError('listLabelsOnIssue', args, e);
|
135 | }
|
136 | }
|
137 |
|
138 | async getPr(prNumber) {
|
139 | this.logger.verbose.info(`Getting info for PR: ${prNumber}`);
|
140 | const args = {
|
141 | owner: this.options.owner,
|
142 | repo: this.options.repo,
|
143 | issue_number: prNumber
|
144 | };
|
145 | this.logger.verbose.info('Getting issue info using:', args);
|
146 | try {
|
147 | const info = await this.github.issues.get(args);
|
148 | this.logger.veryVerbose.info('Got response for "issues.get":\n', info);
|
149 | return info;
|
150 | }
|
151 | catch (e) {
|
152 | throw new GitAPIError('getPr', args, e);
|
153 | }
|
154 | }
|
155 |
|
156 | async getCommit(sha) {
|
157 | this.logger.verbose.info(`Getting info for commit: ${sha}`);
|
158 | try {
|
159 | const info = await this.github.repos.getCommit({
|
160 | owner: this.options.owner,
|
161 | repo: this.options.repo,
|
162 | ref: sha
|
163 | });
|
164 | this.logger.veryVerbose.info('Got response for "repos.getCommit":\n', info);
|
165 | return info;
|
166 | }
|
167 | catch (e) {
|
168 | throw new GitAPIError('getCommit', [], e);
|
169 | }
|
170 | }
|
171 |
|
172 | async getProjectLabels() {
|
173 | this.logger.verbose.info(`Getting labels for project: ${this.options.repo}`);
|
174 | const args = {
|
175 | owner: this.options.owner,
|
176 | repo: this.options.repo
|
177 | };
|
178 | try {
|
179 | const labels = await this.github.issues.listLabelsForRepo(args);
|
180 | this.logger.veryVerbose.info('Got response for "getProjectLabels":\n', labels);
|
181 | this.logger.verbose.info('Found labels on project:\n', labels.data);
|
182 | return labels.data.map(l => l.name);
|
183 | }
|
184 | catch (e) {
|
185 | throw new GitAPIError('getProjectLabels', args, e);
|
186 | }
|
187 | }
|
188 |
|
189 | async getGitLog(start, end = 'HEAD') {
|
190 | try {
|
191 | const log = await gitlog({
|
192 | repo: process.cwd(),
|
193 | number: Number.MAX_SAFE_INTEGER,
|
194 | fields: ['hash', 'authorName', 'authorEmail', 'rawBody'],
|
195 | branch: `${start.trim()}..${end.trim()}`,
|
196 | execOptions: { maxBuffer: 1000 * 1024 }
|
197 | });
|
198 | return log.map(commit => ({
|
199 | hash: commit.hash,
|
200 | authorName: commit.authorName,
|
201 | authorEmail: commit.authorEmail,
|
202 | subject: commit.rawBody,
|
203 | files: (commit.files || []).map(file => path_1.default.resolve(file))
|
204 | }));
|
205 | }
|
206 | catch (error) {
|
207 | const tag = error.match(/ambiguous argument '(\S+)\.\.\S+'/);
|
208 | if (tag) {
|
209 | this.logger.log.error(endent_1.default `
|
210 | Missing tag "${tag[1]}" so the command could not run.
|
211 |
|
212 | To fix this run the following command:
|
213 |
|
214 | git fetch --tags\n
|
215 | `);
|
216 | process.exit(1);
|
217 | }
|
218 | throw new Error(error);
|
219 | }
|
220 | }
|
221 |
|
222 | async getUserByEmail(email) {
|
223 | var _a;
|
224 | try {
|
225 | const search = (await this.github.search.users({
|
226 | q: `in:email ${email}`
|
227 | })).data;
|
228 | return ((_a = search) === null || _a === void 0 ? void 0 : _a.items.length) > 0 ? search.items[0] : {};
|
229 | }
|
230 | catch (error) {
|
231 | this.logger.verbose.warn(`Could not find user by email: ${email}`);
|
232 | }
|
233 | }
|
234 |
|
235 | async getUserByUsername(username) {
|
236 | try {
|
237 | const user = await this.github.users.getByUsername({
|
238 | username
|
239 | });
|
240 | return user.data;
|
241 | }
|
242 | catch (error) {
|
243 | this.logger.verbose.warn(`Could not find user by username: ${username}`);
|
244 | }
|
245 | }
|
246 |
|
247 | async getPullRequest(pr) {
|
248 | this.logger.verbose.info(`Getting Pull Request: ${pr}`);
|
249 | const args = {
|
250 | owner: this.options.owner,
|
251 | repo: this.options.repo,
|
252 | pull_number: pr
|
253 | };
|
254 | this.logger.verbose.info('Getting pull request info using:', args);
|
255 | const result = await this.github.pulls.get(args);
|
256 | this.logger.veryVerbose.info('Got pull request data\n', result);
|
257 | this.logger.verbose.info('Got pull request info');
|
258 | return result;
|
259 | }
|
260 |
|
261 | async searchRepo(options) {
|
262 | const repo = `repo:${this.options.owner}/${this.options.repo}`;
|
263 | options.q = `${repo} ${options.q}`;
|
264 | this.logger.verbose.info('Searching repo using:\n', options);
|
265 | const result = await this.github.search.issuesAndPullRequests(options);
|
266 | this.logger.veryVerbose.info('Got response from search\n', result);
|
267 | this.logger.verbose.info('Searched repo on GitHub.');
|
268 | return result.data;
|
269 | }
|
270 |
|
271 | async graphql(query) {
|
272 | this.logger.verbose.info('Querying Github using GraphQL:\n', query);
|
273 | const data = await graphql_1.graphql(query, {
|
274 | baseUrl: this.graphqlBaseUrl,
|
275 | headers: {
|
276 | authorization: `token ${this.options.token}`
|
277 | }
|
278 | });
|
279 | this.logger.veryVerbose.info('Got response from query\n', data);
|
280 | return data;
|
281 | }
|
282 |
|
283 | async createStatus(prInfo) {
|
284 | const args = Object.assign(Object.assign({}, prInfo), { owner: this.options.owner, repo: this.options.repo });
|
285 | this.logger.verbose.info('Creating status using:\n', args);
|
286 | const result = await this.github.repos.createStatus(args);
|
287 | this.logger.veryVerbose.info('Got response from createStatues\n', result);
|
288 | this.logger.verbose.info('Created status on GitHub.');
|
289 | return result;
|
290 | }
|
291 |
|
292 | async createLabel(label) {
|
293 | this.logger.verbose.info(`Creating "${label.releaseType || 'general'}" label :\n${label.name}`);
|
294 | const color = label.color
|
295 | ? tinycolor2_1.default(label.color).toString('hex6')
|
296 | : tinycolor2_1.default.random().toString('hex6');
|
297 | const result = await this.github.issues.createLabel({
|
298 | name: label.name,
|
299 | owner: this.options.owner,
|
300 | repo: this.options.repo,
|
301 | color: color.replace('#', ''),
|
302 | description: label.description
|
303 | });
|
304 | this.logger.veryVerbose.info('Got response from createLabel\n', result);
|
305 | this.logger.verbose.info('Created label on GitHub.');
|
306 | return result;
|
307 | }
|
308 |
|
309 | async updateLabel(label) {
|
310 | this.logger.verbose.info(`Updating "${label.releaseType || 'generic'}" label :\n${label.name}`);
|
311 | const color = label.color
|
312 | ? tinycolor2_1.default(label.color).toString('hex6')
|
313 | : tinycolor2_1.default.random().toString('hex6');
|
314 | const result = await this.github.issues.updateLabel({
|
315 | current_name: label.name,
|
316 | name: label.name,
|
317 | owner: this.options.owner,
|
318 | repo: this.options.repo,
|
319 | color: color.replace('#', ''),
|
320 | description: label.description
|
321 | });
|
322 | this.logger.veryVerbose.info('Got response from updateLabel\n', result);
|
323 | this.logger.verbose.info('Updated label on GitHub.');
|
324 | return result;
|
325 | }
|
326 |
|
327 | async addLabelToPr(pr, label) {
|
328 | this.logger.verbose.info(`Creating "${label}" label to PR ${pr}`);
|
329 | const result = await this.github.issues.addLabels({
|
330 | issue_number: pr,
|
331 | owner: this.options.owner,
|
332 | repo: this.options.repo,
|
333 | labels: [label]
|
334 | });
|
335 | this.logger.veryVerbose.info('Got response from addLabels\n', result);
|
336 | this.logger.verbose.info('Added labels on Pull Request.');
|
337 | return result;
|
338 | }
|
339 |
|
340 | async removeLabel(pr, label) {
|
341 | this.logger.verbose.info(`Removing "${label}" from #${pr}`);
|
342 | const result = await this.github.issues.removeLabel({
|
343 | issue_number: pr,
|
344 | owner: this.options.owner,
|
345 | repo: this.options.repo,
|
346 | name: label
|
347 | });
|
348 | this.logger.veryVerbose.info('Got response from removeLabel\n', result);
|
349 | this.logger.verbose.info('Removed label on Pull Request.');
|
350 | return result;
|
351 | }
|
352 |
|
353 | async lockIssue(issue) {
|
354 | this.logger.verbose.info(`Locking #${issue} issue...`);
|
355 | const result = await this.github.issues.lock({
|
356 | issue_number: issue,
|
357 | owner: this.options.owner,
|
358 | repo: this.options.repo
|
359 | });
|
360 | this.logger.veryVerbose.info('Got response from lock\n', result);
|
361 | this.logger.verbose.info('Locked issue.');
|
362 | return result;
|
363 | }
|
364 |
|
365 | async getProject() {
|
366 | this.logger.verbose.info('Getting project from GitHub');
|
367 | const result = (await this.github.repos.get({
|
368 | owner: this.options.owner,
|
369 | repo: this.options.repo
|
370 | })).data;
|
371 | this.logger.veryVerbose.info('Got response from repos\n', result);
|
372 | this.logger.verbose.info('Got project information.');
|
373 | return result;
|
374 | }
|
375 |
|
376 | async getPullRequests(options) {
|
377 | this.logger.verbose.info('Getting pull requests...');
|
378 | const result = (await this.github.pulls.list(Object.assign({ owner: this.options.owner.toLowerCase(), repo: this.options.repo.toLowerCase() }, options))).data;
|
379 | this.logger.veryVerbose.info('Got response from pull requests', result);
|
380 | this.logger.verbose.info('Got pull request');
|
381 | return result;
|
382 | }
|
383 |
|
384 | async getCommitsForPR(pr) {
|
385 | this.logger.verbose.info(`Getting commits for PR #${pr}`);
|
386 | const result = await this.github.paginate(this.github.pulls.listCommits.endpoint({
|
387 | owner: this.options.owner.toLowerCase(),
|
388 | repo: this.options.repo.toLowerCase(),
|
389 | pull_number: pr
|
390 | }));
|
391 | this.logger.veryVerbose.info(`Got response from PR #${pr}\n`, result);
|
392 | this.logger.verbose.info(`Got commits for PR #${pr}.`);
|
393 | return result;
|
394 | }
|
395 |
|
396 | async getCommentId(pr, context = 'default') {
|
397 | const commentIdentifier = makeCommentIdentifier(context);
|
398 | this.logger.verbose.info('Getting previous comments on:', pr);
|
399 | const comments = await this.github.issues.listComments({
|
400 | owner: this.options.owner,
|
401 | repo: this.options.repo,
|
402 | issue_number: pr
|
403 | });
|
404 | this.logger.veryVerbose.info('Got PR comments\n', comments);
|
405 | const oldMessage = comments.data.find(comment => comment.body.includes(commentIdentifier));
|
406 | if (!oldMessage) {
|
407 | return -1;
|
408 | }
|
409 | this.logger.verbose.info('Found previous message from same scope.');
|
410 | return oldMessage.id;
|
411 | }
|
412 |
|
413 | async deleteComment(pr, context = 'default') {
|
414 | const commentId = await this.getCommentId(pr, context);
|
415 | if (commentId === -1) {
|
416 | return;
|
417 | }
|
418 | this.logger.verbose.info(`Deleting comment: ${commentId}`);
|
419 | await this.github.issues.deleteComment({
|
420 | owner: this.options.owner,
|
421 | repo: this.options.repo,
|
422 | comment_id: commentId
|
423 | });
|
424 | this.logger.verbose.info(`Successfully deleted comment: ${commentId}`);
|
425 | }
|
426 |
|
427 | async createComment(message, pr, context = 'default') {
|
428 | const commentIdentifier = makeCommentIdentifier(context);
|
429 | this.logger.verbose.info('Using comment identifier:', commentIdentifier);
|
430 | await this.deleteComment(pr, context);
|
431 | this.logger.verbose.info('Creating new comment');
|
432 | const result = await this.github.issues.createComment({
|
433 | owner: this.options.owner,
|
434 | repo: this.options.repo,
|
435 | issue_number: pr,
|
436 | body: `${commentIdentifier}\n${message}`
|
437 | });
|
438 | this.logger.veryVerbose.info('Got response from creating comment\n', result);
|
439 | this.logger.verbose.info('Successfully posted comment to PR');
|
440 | return result;
|
441 | }
|
442 |
|
443 | async editComment(message, pr, context = 'default') {
|
444 | const commentIdentifier = makeCommentIdentifier(context);
|
445 | this.logger.verbose.info('Using comment identifier:', commentIdentifier);
|
446 | const commentId = await this.getCommentId(pr, context);
|
447 | if (commentId === -1) {
|
448 | return this.createComment(message, pr, context);
|
449 | }
|
450 | this.logger.verbose.info('Editing comment');
|
451 | const result = await this.github.issues.updateComment({
|
452 | owner: this.options.owner,
|
453 | repo: this.options.repo,
|
454 | comment_id: commentId,
|
455 | body: `${commentIdentifier}\n${message}`
|
456 | });
|
457 | this.logger.veryVerbose.info('Got response from editing comment\n', result);
|
458 | this.logger.verbose.info('Successfully edited comment on PR');
|
459 | return result;
|
460 | }
|
461 |
|
462 | async addToPrBody(message, pr, context = 'default') {
|
463 | const id = makePrBodyIdentifier(context);
|
464 | this.logger.verbose.info('Using PR body identifier:', id);
|
465 | this.logger.verbose.info('Getting previous pr body on:', pr);
|
466 | const issue = await this.github.issues.get({
|
467 | owner: this.options.owner,
|
468 | repo: this.options.repo,
|
469 | issue_number: pr
|
470 | });
|
471 | this.logger.veryVerbose.info('Got PR description\n', issue.data.body);
|
472 | const regex = new RegExp(`(${id})\\s*([\\S\\s]*)\\s*(${id})`);
|
473 | let body = issue.data.body;
|
474 | if (body.match(regex)) {
|
475 | this.logger.verbose.info('Found previous message from same scope.');
|
476 | this.logger.verbose.info('Replacing pr body comment');
|
477 | body = body.replace(regex, message ? `$1\n${message}\n$3` : '');
|
478 | }
|
479 | else {
|
480 | body += message ? `\n${id}\n${message}\n${id}\n` : '';
|
481 | }
|
482 | this.logger.verbose.info('Creating new pr body');
|
483 | const result = await this.github.issues.update({
|
484 | owner: this.options.owner,
|
485 | repo: this.options.repo,
|
486 | issue_number: pr,
|
487 | body
|
488 | });
|
489 | this.logger.veryVerbose.info('Got response from updating body\n', result);
|
490 | this.logger.verbose.info(`Successfully updated body of PR #${pr}`);
|
491 | return result;
|
492 | }
|
493 |
|
494 | async publish(releaseNotes, tag, prerelease = false) {
|
495 | this.logger.verbose.info('Creating release on GitHub for tag:', tag);
|
496 | const result = await this.github.repos.createRelease({
|
497 | owner: this.options.owner,
|
498 | repo: this.options.repo,
|
499 | tag_name: tag,
|
500 | body: releaseNotes,
|
501 | prerelease
|
502 | });
|
503 | this.logger.veryVerbose.info('Got response from createRelease\n', result);
|
504 | this.logger.verbose.info('Created GitHub release.');
|
505 | return result;
|
506 | }
|
507 |
|
508 | async getLatestTagInBranch() {
|
509 | return exec_promise_1.default('git', ['describe', '--tags', '--abbrev=0']);
|
510 | }
|
511 | }
|
512 | tslib_1.__decorate([
|
513 | typescript_memoize_1.Memoize()
|
514 | ], Git.prototype, "getLatestReleaseInfo", null);
|
515 | tslib_1.__decorate([
|
516 | typescript_memoize_1.Memoize()
|
517 | ], Git.prototype, "getLatestRelease", null);
|
518 | tslib_1.__decorate([
|
519 | typescript_memoize_1.Memoize()
|
520 | ], Git.prototype, "getLabels", null);
|
521 | tslib_1.__decorate([
|
522 | typescript_memoize_1.Memoize()
|
523 | ], Git.prototype, "getPr", null);
|
524 | tslib_1.__decorate([
|
525 | typescript_memoize_1.Memoize()
|
526 | ], Git.prototype, "getCommit", null);
|
527 | tslib_1.__decorate([
|
528 | typescript_memoize_1.Memoize()
|
529 | ], Git.prototype, "getGitLog", null);
|
530 | tslib_1.__decorate([
|
531 | typescript_memoize_1.Memoize()
|
532 | ], Git.prototype, "getUserByEmail", null);
|
533 | tslib_1.__decorate([
|
534 | typescript_memoize_1.Memoize()
|
535 | ], Git.prototype, "getUserByUsername", null);
|
536 | tslib_1.__decorate([
|
537 | typescript_memoize_1.Memoize()
|
538 | ], Git.prototype, "getPullRequest", null);
|
539 | tslib_1.__decorate([
|
540 | typescript_memoize_1.Memoize()
|
541 | ], Git.prototype, "getProject", null);
|
542 | tslib_1.__decorate([
|
543 | typescript_memoize_1.Memoize()
|
544 | ], Git.prototype, "getCommitsForPR", null);
|
545 | exports.default = Git;
|
546 |
|
\ | No newline at end of file |