release-please 17.4.1 → 17.5.1

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 (34) hide show
  1. package/build/src/bin/release-please.js +39 -10
  2. package/build/src/bootstrapper.d.ts +2 -2
  3. package/build/src/changelog-notes/default.js +9 -1
  4. package/build/src/changelog-notes/github.d.ts +2 -2
  5. package/build/src/changelog-notes.d.ts +1 -0
  6. package/build/src/commit.d.ts +6 -0
  7. package/build/src/factories/changelog-notes-factory.d.ts +2 -2
  8. package/build/src/factories/plugin-factory.d.ts +2 -2
  9. package/build/src/factories/versioning-strategy-factory.d.ts +2 -2
  10. package/build/src/factory.d.ts +2 -2
  11. package/build/src/github-api.d.ts +260 -0
  12. package/build/src/github-api.js +701 -0
  13. package/build/src/github.d.ts +21 -171
  14. package/build/src/github.js +154 -699
  15. package/build/src/index.d.ts +2 -2
  16. package/build/src/index.js +1 -1
  17. package/build/src/local-github.d.ts +271 -0
  18. package/build/src/local-github.js +778 -0
  19. package/build/src/manifest.d.ts +7 -5
  20. package/build/src/manifest.js +28 -26
  21. package/build/src/plugin.d.ts +3 -3
  22. package/build/src/plugins/group-priority.d.ts +2 -2
  23. package/build/src/plugins/linked-versions.d.ts +2 -2
  24. package/build/src/plugins/maven-workspace.d.ts +2 -2
  25. package/build/src/plugins/merge.d.ts +2 -2
  26. package/build/src/plugins/node-workspace.d.ts +2 -2
  27. package/build/src/plugins/sentence-case.d.ts +2 -2
  28. package/build/src/plugins/workspace.d.ts +2 -2
  29. package/build/src/scm.d.ts +80 -0
  30. package/build/src/scm.js +16 -0
  31. package/build/src/strategies/base.d.ts +5 -3
  32. package/build/src/strategies/base.js +2 -0
  33. package/build/src/util/pull-request-overflow-handler.d.ts +2 -2
  34. package/package.json +2 -2
@@ -13,23 +13,17 @@
13
13
  // See the License for the specific language governing permissions and
14
14
  // limitations under the License.
15
15
  Object.defineProperty(exports, "__esModule", { value: true });
16
- exports.sleepInMs = exports.GitHub = exports.GH_GRAPHQL_URL = exports.GH_API_URL = void 0;
17
- const code_suggester_1 = require("code-suggester");
18
- const rest_1 = require("@octokit/rest");
19
- const request_1 = require("@octokit/request");
20
- const graphql_1 = require("@octokit/graphql");
16
+ exports.sleepInMs = exports.GitHub = void 0;
21
17
  const request_error_1 = require("@octokit/request-error");
18
+ const code_suggester_1 = require("code-suggester");
22
19
  const errors_1 = require("./errors");
23
20
  const MAX_ISSUE_BODY_SIZE = 65536;
24
21
  const MAX_SLEEP_SECONDS = 20;
25
- exports.GH_API_URL = 'https://api.github.com';
26
- exports.GH_GRAPHQL_URL = 'https://api.github.com';
27
22
  const logger_1 = require("./util/logger");
28
23
  const manifest_1 = require("./manifest");
24
+ const github_api_1 = require("./github-api");
29
25
  const signoff_commit_message_1 = require("./util/signoff-commit-message");
30
26
  const git_file_utils_1 = require("@google-automations/git-file-utils");
31
- const https_proxy_agent_1 = require("https-proxy-agent");
32
- const http_proxy_agent_1 = require("http-proxy-agent");
33
27
  const composite_1 = require("./updaters/composite");
34
28
  class GitHub {
35
29
  constructor(options) {
@@ -133,130 +127,6 @@ class GitHub {
133
127
  this.logger.debug(`finding files by glob: ${glob}, ref: ${ref}, prefix: ${prefix}`);
134
128
  return await this.fileCache.findFilesByGlob(glob, ref, prefix);
135
129
  });
136
- /**
137
- * Open a pull request
138
- *
139
- * @param {PullRequest} pullRequest Pull request data to update
140
- * @param {string} targetBranch The base branch of the pull request
141
- * @param {string} message The commit message for the commit
142
- * @param {Update[]} updates The files to update
143
- * @param {CreatePullRequestOptions} options The pull request options
144
- * @throws {GitHubAPIError} on an API error
145
- */
146
- this.createPullRequest = wrapAsync(async (pullRequest, targetBranch, message, updates, options) => {
147
- // Update the files for the release if not already supplied
148
- const changes = await this.buildChangeSet(updates, targetBranch);
149
- const prNumber = await (0, code_suggester_1.createPullRequest)(this.octokit, changes, {
150
- upstreamOwner: this.repository.owner,
151
- upstreamRepo: this.repository.repo,
152
- title: pullRequest.title,
153
- branch: pullRequest.headBranchName,
154
- description: pullRequest.body,
155
- primary: targetBranch,
156
- force: true,
157
- fork: !!(options === null || options === void 0 ? void 0 : options.fork),
158
- message,
159
- logger: this.logger,
160
- draft: !!(options === null || options === void 0 ? void 0 : options.draft),
161
- labels: pullRequest.labels,
162
- });
163
- if (prNumber === 0) {
164
- this.logger.warn('no code changes detected, skipping pull request creation');
165
- return {
166
- headBranchName: pullRequest.headBranchName,
167
- baseBranchName: targetBranch,
168
- number: 0,
169
- title: pullRequest.title,
170
- body: pullRequest.body,
171
- labels: pullRequest.labels,
172
- files: [],
173
- };
174
- }
175
- return await this.getPullRequest(prNumber);
176
- });
177
- /**
178
- * Fetch a pull request given the pull number
179
- * @param {number} number The pull request number
180
- * @returns {PullRequest}
181
- */
182
- this.getPullRequest = wrapAsync(async (number) => {
183
- const response = await this.octokit.pulls.get({
184
- owner: this.repository.owner,
185
- repo: this.repository.repo,
186
- pull_number: number,
187
- });
188
- return {
189
- headBranchName: response.data.head.ref,
190
- baseBranchName: response.data.base.ref,
191
- number: response.data.number,
192
- title: response.data.title,
193
- body: response.data.body || '',
194
- files: [],
195
- labels: response.data.labels
196
- .map(label => label.name)
197
- .filter(name => !!name),
198
- };
199
- });
200
- /**
201
- * Update a pull request's title and body.
202
- * @param {number} number The pull request number
203
- * @param {ReleasePullRequest} releasePullRequest Pull request data to update
204
- * @param {string} targetBranch The target branch of the pull request
205
- * @param {string} options.signoffUser Optional. Commit signoff message
206
- * @param {boolean} options.fork Optional. Whether to open the pull request from
207
- * a fork or not. Defaults to `false`
208
- * @param {PullRequestOverflowHandler} options.pullRequestOverflowHandler Optional.
209
- * Handles extra large pull request body messages.
210
- */
211
- this.updatePullRequest = wrapAsync(async (number, releasePullRequest, targetBranch, options) => {
212
- // Update the files for the release if not already supplied
213
- const changes = await this.buildChangeSet(releasePullRequest.updates, targetBranch);
214
- let message = releasePullRequest.title.toString();
215
- if (options === null || options === void 0 ? void 0 : options.signoffUser) {
216
- message = (0, signoff_commit_message_1.signoffCommitMessage)(message, options.signoffUser);
217
- }
218
- const title = releasePullRequest.title.toString();
219
- const body = ((options === null || options === void 0 ? void 0 : options.pullRequestOverflowHandler)
220
- ? await options.pullRequestOverflowHandler.handleOverflow(releasePullRequest)
221
- : releasePullRequest.body)
222
- .toString()
223
- .slice(0, MAX_ISSUE_BODY_SIZE);
224
- const prNumber = await (0, code_suggester_1.createPullRequest)(this.octokit, changes, {
225
- upstreamOwner: this.repository.owner,
226
- upstreamRepo: this.repository.repo,
227
- title,
228
- branch: releasePullRequest.headRefName,
229
- description: body,
230
- primary: targetBranch,
231
- force: true,
232
- fork: (options === null || options === void 0 ? void 0 : options.fork) === false ? false : true,
233
- message,
234
- logger: this.logger,
235
- draft: releasePullRequest.draft,
236
- });
237
- if (prNumber !== number) {
238
- this.logger.warn(`updated code for ${prNumber}, but update requested for ${number}`);
239
- }
240
- const response = await this.octokit.pulls.update({
241
- owner: this.repository.owner,
242
- repo: this.repository.repo,
243
- pull_number: number,
244
- title: releasePullRequest.title.toString(),
245
- body,
246
- state: 'open',
247
- });
248
- return {
249
- headBranchName: response.data.head.ref,
250
- baseBranchName: response.data.base.ref,
251
- number: response.data.number,
252
- title: response.data.title,
253
- body: response.data.body || '',
254
- files: [],
255
- labels: response.data.labels
256
- .map(label => label.name)
257
- .filter(name => !!name),
258
- };
259
- });
260
130
  /**
261
131
  * Returns a list of paths to all files with a given file
262
132
  * extension.
@@ -277,213 +147,27 @@ class GitHub {
277
147
  }
278
148
  return this.fileCache.findFilesByExtension(extension, ref, prefix);
279
149
  });
280
- /**
281
- * Create a GitHub release
282
- *
283
- * @param {Release} release Release parameters
284
- * @param {ReleaseOptions} options Release option parameters
285
- * @throws {DuplicateReleaseError} if the release tag already exists
286
- * @throws {GitHubAPIError} on other API errors
287
- */
288
- this.createRelease = wrapAsync(async (release, options = {}) => {
289
- if (options.forceTag) {
290
- try {
291
- await this.octokit.git.createRef({
292
- owner: this.repository.owner,
293
- repo: this.repository.repo,
294
- ref: `refs/tags/${release.tag.toString()}`,
295
- sha: release.sha,
296
- });
297
- }
298
- catch (err) {
299
- // ignore if tag already exists
300
- if (err.status === 422) {
301
- this.logger.debug(`Tag ${release.tag.toString()} already exists, skipping tag creation`);
302
- }
303
- else {
304
- throw err;
305
- }
306
- }
307
- }
308
- const resp = await this.octokit.repos.createRelease({
309
- name: release.name,
310
- owner: this.repository.owner,
311
- repo: this.repository.repo,
312
- tag_name: release.tag.toString(),
313
- body: release.notes,
314
- draft: !!options.draft,
315
- prerelease: !!options.prerelease,
316
- target_commitish: release.sha,
317
- });
318
- return {
319
- id: resp.data.id,
320
- name: resp.data.name || undefined,
321
- tagName: resp.data.tag_name,
322
- sha: resp.data.target_commitish,
323
- notes: resp.data.body_text ||
324
- resp.data.body ||
325
- resp.data.body_html ||
326
- undefined,
327
- url: resp.data.html_url,
328
- draft: resp.data.draft,
329
- uploadUrl: resp.data.upload_url,
330
- };
331
- }, e => {
332
- if (e instanceof request_error_1.RequestError) {
333
- if (e.status === 422 &&
334
- errors_1.GitHubAPIError.parseErrors(e).some(error => {
335
- return error.code === 'already_exists';
336
- })) {
337
- throw new errors_1.DuplicateReleaseError(e, 'tagName');
338
- }
339
- }
340
- });
341
- /**
342
- * Makes a comment on a issue/pull request.
343
- *
344
- * @param {string} comment - The body of the comment to post.
345
- * @param {number} number - The issue or pull request number.
346
- * @throws {GitHubAPIError} on an API error
347
- */
348
- this.commentOnIssue = wrapAsync(async (comment, number) => {
349
- this.logger.debug(`adding comment to https://github.com/${this.repository.owner}/${this.repository.repo}/issues/${number}`);
350
- const resp = await this.octokit.issues.createComment({
351
- owner: this.repository.owner,
352
- repo: this.repository.repo,
353
- issue_number: number,
354
- body: comment,
355
- });
356
- return resp.data.html_url;
357
- });
358
- /**
359
- * Removes labels from an issue/pull request.
360
- *
361
- * @param {string[]} labels The labels to remove.
362
- * @param {number} number The issue/pull request number.
363
- */
364
- this.removeIssueLabels = wrapAsync(async (labels, number) => {
365
- if (labels.length === 0) {
366
- return;
367
- }
368
- this.logger.debug(`removing labels: ${labels} from issue/pull ${number}`);
369
- await Promise.all(labels.map(label => this.octokit.issues.removeLabel({
370
- owner: this.repository.owner,
371
- repo: this.repository.repo,
372
- issue_number: number,
373
- name: label,
374
- })));
375
- });
376
- /**
377
- * Adds label to an issue/pull request.
378
- *
379
- * @param {string[]} labels The labels to add.
380
- * @param {number} number The issue/pull request number.
381
- */
382
- this.addIssueLabels = wrapAsync(async (labels, number) => {
383
- if (labels.length === 0) {
384
- return;
385
- }
386
- this.logger.debug(`adding labels: ${labels} from issue/pull ${number}`);
387
- await this.octokit.issues.addLabels({
388
- owner: this.repository.owner,
389
- repo: this.repository.repo,
390
- issue_number: number,
391
- labels,
392
- });
393
- });
394
150
  this.repository = options.repository;
395
151
  this.octokit = options.octokitAPIs.octokit;
396
- this.request = options.octokitAPIs.request;
397
152
  this.graphql = options.octokitAPIs.graphql;
398
153
  this.fileCache = new git_file_utils_1.RepositoryFileCache(this.octokit, this.repository);
399
154
  this.logger = (_a = options.logger) !== null && _a !== void 0 ? _a : logger_1.logger;
155
+ this.gitHubApi = new github_api_1.GitHubApi({
156
+ repository: this.repository,
157
+ octokitAPIs: options.octokitAPIs,
158
+ logger: this.logger,
159
+ });
400
160
  }
401
- static createDefaultAgent(baseUrl, defaultProxy) {
402
- if (!defaultProxy) {
403
- return undefined;
404
- }
405
- const { host, port } = defaultProxy;
406
- if (new URL(baseUrl).protocol.replace(':', '') === 'http') {
407
- return new http_proxy_agent_1.HttpProxyAgent(`http://${host}:${port}`);
408
- }
409
- else {
410
- return new https_proxy_agent_1.HttpsProxyAgent(`https://${host}:${port}`);
411
- }
161
+ getGitHubApi() {
162
+ return this.gitHubApi;
412
163
  }
413
- /**
414
- * Build a new GitHub client with auto-detected default branch.
415
- *
416
- * @param {GitHubCreateOptions} options Configuration options
417
- * @param {string} options.owner The repository owner.
418
- * @param {string} options.repo The repository name.
419
- * @param {string} options.defaultBranch Optional. The repository's default branch.
420
- * Defaults to the value fetched via the API.
421
- * @param {string} options.apiUrl Optional. The base url of the GitHub API.
422
- * @param {string} options.graphqlUrl Optional. The base url of the GraphQL API.
423
- * @param {OctokitAPISs} options.octokitAPIs Optional. Override the internal
424
- * client instances with a pre-authenticated instance.
425
- * @param {string} token Optional. A GitHub API token used for authentication.
426
- */
427
164
  static async create(options) {
428
- var _a, _b, _c, _d;
429
- const apiUrl = (_a = options.apiUrl) !== null && _a !== void 0 ? _a : exports.GH_API_URL;
430
- const graphqlUrl = (_b = options.graphqlUrl) !== null && _b !== void 0 ? _b : exports.GH_GRAPHQL_URL;
431
- const releasePleaseVersion = require('../../package.json').version;
432
- const apis = (_c = options.octokitAPIs) !== null && _c !== void 0 ? _c : {
433
- octokit: new rest_1.Octokit({
434
- baseUrl: apiUrl,
435
- auth: options.token,
436
- request: {
437
- agent: this.createDefaultAgent(apiUrl, options.proxy),
438
- fetch: options.fetch,
439
- },
440
- }),
441
- request: request_1.request.defaults({
442
- baseUrl: apiUrl,
443
- headers: {
444
- 'user-agent': `release-please/${releasePleaseVersion}`,
445
- Authorization: `token ${options.token}`,
446
- },
447
- fetch: options.fetch,
448
- }),
449
- graphql: graphql_1.graphql.defaults({
450
- baseUrl: graphqlUrl,
451
- request: {
452
- agent: this.createDefaultAgent(graphqlUrl, options.proxy),
453
- fetch: options.fetch,
454
- },
455
- headers: {
456
- 'user-agent': `release-please/${releasePleaseVersion}`,
457
- Authorization: `token ${options.token}`,
458
- 'content-type': 'application/vnd.github.v3+json',
459
- },
460
- }),
461
- };
462
- const opts = {
463
- repository: {
464
- owner: options.owner,
465
- repo: options.repo,
466
- defaultBranch: (_d = options.defaultBranch) !== null && _d !== void 0 ? _d : (await GitHub.defaultBranch(options.owner, options.repo, apis.octokit)),
467
- },
468
- octokitAPIs: apis,
165
+ const gitHubApi = await github_api_1.GitHubApi.create(options);
166
+ return new GitHub({
167
+ repository: gitHubApi.repository,
168
+ octokitAPIs: gitHubApi.octokitAPIs,
469
169
  logger: options.logger,
470
- };
471
- return new GitHub(opts);
472
- }
473
- /**
474
- * Returns the default branch for a given repository.
475
- *
476
- * @param {string} owner The GitHub repository owner
477
- * @param {string} repo The GitHub repository name
478
- * @param {OctokitType} octokit An authenticated octokit instance
479
- * @returns {string} Name of the default branch
480
- */
481
- static async defaultBranch(owner, repo, octokit) {
482
- const { data } = await octokit.repos.get({
483
- repo,
484
- owner,
485
170
  });
486
- return data.default_branch;
487
171
  }
488
172
  /**
489
173
  * Returns the list of commits to the default branch after the provided filter
@@ -545,8 +229,8 @@ class GitHub {
545
229
  }
546
230
  }
547
231
  async mergeCommitsGraphQL(targetBranch, cursor, options = {}) {
548
- var _a, _b, _c, _d, _e, _f, _g, _h, _j;
549
- var _k;
232
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
233
+ var _l;
550
234
  this.logger.debug(`Fetching merge commits on branch ${targetBranch} with cursor: ${cursor}`);
551
235
  const query = `query pullRequestsSince($owner: String!, $repo: String!, $num: Int!, $maxFilesChanged: Int, $targetBranch: String!, $cursor: String) {
552
236
  repository(owner: $owner, name: $repo) {
@@ -583,6 +267,13 @@ class GitHub {
583
267
  }
584
268
  sha: oid
585
269
  message
270
+ author {
271
+ name
272
+ email
273
+ user {
274
+ login
275
+ }
276
+ }
586
277
  }
587
278
  pageInfo {
588
279
  hasNextPage
@@ -624,7 +315,7 @@ class GitHub {
624
315
  for (const commit of commits) {
625
316
  for (const pr of commit.associatedPullRequests.nodes) {
626
317
  if ((_c = pr.mergeCommit) === null || _c === void 0 ? void 0 : _c.oid) {
627
- (_d = mergeCommitCount[_k = pr.mergeCommit.oid]) !== null && _d !== void 0 ? _d : (mergeCommitCount[_k] = 0);
318
+ (_d = mergeCommitCount[_l = pr.mergeCommit.oid]) !== null && _d !== void 0 ? _d : (mergeCommitCount[_l] = 0);
628
319
  mergeCommitCount[pr.mergeCommit.oid]++;
629
320
  }
630
321
  }
@@ -634,6 +325,13 @@ class GitHub {
634
325
  const commit = {
635
326
  sha: graphCommit.sha,
636
327
  message: graphCommit.message,
328
+ author: graphCommit.author
329
+ ? {
330
+ name: graphCommit.author.name || 'Unknown',
331
+ email: graphCommit.author.email,
332
+ username: (_e = graphCommit.author.user) === null || _e === void 0 ? void 0 : _e.login,
333
+ }
334
+ : undefined,
637
335
  };
638
336
  const mergePullRequest = graphCommit.associatedPullRequests.nodes.find(pr => {
639
337
  return (
@@ -654,15 +352,15 @@ class GitHub {
654
352
  number: pullRequest.number,
655
353
  baseBranchName: pullRequest.baseRefName,
656
354
  headBranchName: pullRequest.headRefName,
657
- mergeCommitOid: (_e = pullRequest.mergeCommit) === null || _e === void 0 ? void 0 : _e.oid,
355
+ mergeCommitOid: (_f = pullRequest.mergeCommit) === null || _f === void 0 ? void 0 : _f.oid,
658
356
  title: pullRequest.title,
659
357
  body: pullRequest.body,
660
358
  labels: pullRequest.labels.nodes.map(node => node.name),
661
- files: (((_f = pullRequest.files) === null || _f === void 0 ? void 0 : _f.nodes) || []).map(node => node.path),
359
+ files: (((_g = pullRequest.files) === null || _g === void 0 ? void 0 : _g.nodes) || []).map(node => node.path),
662
360
  };
663
361
  }
664
362
  if (mergePullRequest) {
665
- if (((_h = (_g = mergePullRequest.files) === null || _g === void 0 ? void 0 : _g.pageInfo) === null || _h === void 0 ? void 0 : _h.hasNextPage) &&
363
+ if (((_j = (_h = mergePullRequest.files) === null || _h === void 0 ? void 0 : _h.pageInfo) === null || _j === void 0 ? void 0 : _j.hasNextPage) &&
666
364
  options.backfillFiles) {
667
365
  this.logger.info(`PR #${mergePullRequest.number} has many files, backfilling`);
668
366
  commit.files = await this.getCommitFiles(graphCommit.sha);
@@ -670,7 +368,7 @@ class GitHub {
670
368
  else {
671
369
  // We cannot directly fetch files on commits via graphql, only provide file
672
370
  // information for commits with associated pull requests
673
- commit.files = (((_j = mergePullRequest.files) === null || _j === void 0 ? void 0 : _j.nodes) || []).map(node => node.path);
371
+ commit.files = (((_k = mergePullRequest.files) === null || _k === void 0 ? void 0 : _k.nodes) || []).map(node => node.path);
674
372
  }
675
373
  }
676
374
  else if (options.backfillFiles) {
@@ -700,167 +398,7 @@ class GitHub {
700
398
  * @throws {GitHubAPIError} on an API error
701
399
  */
702
400
  async *pullRequestIterator(targetBranch, status = 'MERGED', maxResults = Number.MAX_SAFE_INTEGER, includeFiles = true) {
703
- const generator = includeFiles
704
- ? this.pullRequestIteratorWithFiles(targetBranch, status, maxResults)
705
- : this.pullRequestIteratorWithoutFiles(targetBranch, status, maxResults);
706
- for await (const pullRequest of generator) {
707
- yield pullRequest;
708
- }
709
- }
710
- /**
711
- * Helper implementation of pullRequestIterator that includes files via
712
- * the graphQL API.
713
- *
714
- * @param {string} targetBranch The base branch of the pull request
715
- * @param {string} status The status of the pull request
716
- * @param {number} maxResults Limit the number of results searched
717
- */
718
- async *pullRequestIteratorWithFiles(targetBranch, status = 'MERGED', maxResults = Number.MAX_SAFE_INTEGER) {
719
- let cursor = undefined;
720
- let results = 0;
721
- while (results < maxResults) {
722
- const response = await this.pullRequestsGraphQL(targetBranch, status, cursor);
723
- // no response usually means we ran out of results
724
- if (!response) {
725
- break;
726
- }
727
- for (let i = 0; i < response.data.length; i++) {
728
- results += 1;
729
- yield response.data[i];
730
- }
731
- if (!response.pageInfo.hasNextPage) {
732
- break;
733
- }
734
- cursor = response.pageInfo.endCursor;
735
- }
736
- }
737
- /**
738
- * Helper implementation of pullRequestIterator that excludes files
739
- * via the REST API.
740
- *
741
- * @param {string} targetBranch The base branch of the pull request
742
- * @param {string} status The status of the pull request
743
- * @param {number} maxResults Limit the number of results searched
744
- */
745
- async *pullRequestIteratorWithoutFiles(targetBranch, status = 'MERGED', maxResults = Number.MAX_SAFE_INTEGER) {
746
- const statusMap = {
747
- OPEN: 'open',
748
- CLOSED: 'closed',
749
- MERGED: 'closed',
750
- };
751
- let results = 0;
752
- for await (const { data: pulls } of this.octokit.paginate.iterator('GET /repos/{owner}/{repo}/pulls', {
753
- state: statusMap[status],
754
- owner: this.repository.owner,
755
- repo: this.repository.repo,
756
- base: targetBranch,
757
- sort: 'updated',
758
- direction: 'desc',
759
- })) {
760
- for (const pull of pulls) {
761
- // The REST API does not have an option for "merged"
762
- // pull requests - they are closed with a `merged_at` timestamp
763
- if (status !== 'MERGED' || pull.merged_at) {
764
- results += 1;
765
- yield {
766
- headBranchName: pull.head.ref,
767
- baseBranchName: pull.base.ref,
768
- number: pull.number,
769
- title: pull.title,
770
- body: pull.body || '',
771
- labels: pull.labels.map(label => label.name),
772
- files: [],
773
- sha: pull.merge_commit_sha || undefined,
774
- };
775
- if (results >= maxResults) {
776
- break;
777
- }
778
- }
779
- }
780
- if (results >= maxResults) {
781
- break;
782
- }
783
- }
784
- }
785
- /**
786
- * Return a list of merged pull requests. The list is not guaranteed to be sorted
787
- * by merged_at, but is generally most recent first.
788
- *
789
- * @param {string} targetBranch - Base branch of the pull request. Defaults to
790
- * the configured default branch.
791
- * @param {number} page - Page of results. Defaults to 1.
792
- * @param {number} perPage - Number of results per page. Defaults to 100.
793
- * @returns {PullRequestHistory | null} - List of merged pull requests
794
- * @throws {GitHubAPIError} on an API error
795
- */
796
- async pullRequestsGraphQL(targetBranch, states = 'MERGED', cursor) {
797
- var _a;
798
- this.logger.debug(`Fetching ${states} pull requests on branch ${targetBranch} with cursor ${cursor}`);
799
- const response = await this.graphqlRequest({
800
- query: `query mergedPullRequests($owner: String!, $repo: String!, $num: Int!, $maxFilesChanged: Int, $targetBranch: String!, $states: [PullRequestState!], $cursor: String) {
801
- repository(owner: $owner, name: $repo) {
802
- pullRequests(first: $num, after: $cursor, baseRefName: $targetBranch, states: $states, orderBy: {field: CREATED_AT, direction: DESC}) {
803
- nodes {
804
- number
805
- title
806
- baseRefName
807
- headRefName
808
- labels(first: 10) {
809
- nodes {
810
- name
811
- }
812
- }
813
- body
814
- mergeCommit {
815
- oid
816
- }
817
- files(first: $maxFilesChanged) {
818
- nodes {
819
- path
820
- }
821
- pageInfo {
822
- endCursor
823
- hasNextPage
824
- }
825
- }
826
- }
827
- pageInfo {
828
- endCursor
829
- hasNextPage
830
- }
831
- }
832
- }
833
- }`,
834
- cursor,
835
- owner: this.repository.owner,
836
- repo: this.repository.repo,
837
- num: 25,
838
- targetBranch,
839
- states,
840
- maxFilesChanged: 64,
841
- });
842
- if (!((_a = response === null || response === void 0 ? void 0 : response.repository) === null || _a === void 0 ? void 0 : _a.pullRequests)) {
843
- this.logger.warn(`Could not find merged pull requests for branch ${targetBranch} - it likely does not exist.`);
844
- return null;
845
- }
846
- const pullRequests = (response.repository.pullRequests.nodes ||
847
- []);
848
- return {
849
- pageInfo: response.repository.pullRequests.pageInfo,
850
- data: pullRequests.map(pullRequest => {
851
- var _a, _b, _c;
852
- return {
853
- sha: (_a = pullRequest.mergeCommit) === null || _a === void 0 ? void 0 : _a.oid,
854
- number: pullRequest.number,
855
- baseBranchName: pullRequest.baseRefName,
856
- headBranchName: pullRequest.headRefName,
857
- labels: (((_b = pullRequest.labels) === null || _b === void 0 ? void 0 : _b.nodes) || []).map(l => l.name),
858
- title: pullRequest.title,
859
- body: pullRequest.body + '',
860
- files: (((_c = pullRequest.files) === null || _c === void 0 ? void 0 : _c.nodes) || []).map(node => node.path),
861
- };
862
- }),
863
- };
401
+ yield* this.gitHubApi.pullRequestIterator(targetBranch, status, maxResults, includeFiles);
864
402
  }
865
403
  /**
866
404
  * Iterate through releases with a max number of results scanned.
@@ -872,80 +410,7 @@ class GitHub {
872
410
  * @throws {GitHubAPIError} on an API error
873
411
  */
874
412
  async *releaseIterator(options = {}) {
875
- var _a;
876
- const maxResults = (_a = options.maxResults) !== null && _a !== void 0 ? _a : Number.MAX_SAFE_INTEGER;
877
- let results = 0;
878
- let cursor = undefined;
879
- while (true) {
880
- const response = await this.releaseGraphQL(cursor);
881
- if (!response) {
882
- break;
883
- }
884
- for (let i = 0; i < response.data.length; i++) {
885
- if ((results += 1) > maxResults) {
886
- break;
887
- }
888
- yield response.data[i];
889
- }
890
- if (results > maxResults || !response.pageInfo.hasNextPage) {
891
- break;
892
- }
893
- cursor = response.pageInfo.endCursor;
894
- }
895
- }
896
- async releaseGraphQL(cursor) {
897
- this.logger.debug(`Fetching releases with cursor ${cursor}`);
898
- const response = await this.graphqlRequest({
899
- query: `query releases($owner: String!, $repo: String!, $num: Int!, $cursor: String) {
900
- repository(owner: $owner, name: $repo) {
901
- releases(first: $num, after: $cursor, orderBy: {field: CREATED_AT, direction: DESC}) {
902
- nodes {
903
- name
904
- tag {
905
- name
906
- }
907
- tagCommit {
908
- oid
909
- }
910
- url
911
- description
912
- isDraft
913
- }
914
- pageInfo {
915
- endCursor
916
- hasNextPage
917
- }
918
- }
919
- }
920
- }`,
921
- cursor,
922
- owner: this.repository.owner,
923
- repo: this.repository.repo,
924
- num: 25,
925
- });
926
- if (!response.repository.releases.nodes.length) {
927
- this.logger.warn('Could not find releases.');
928
- return null;
929
- }
930
- const releases = response.repository.releases.nodes;
931
- return {
932
- pageInfo: response.repository.releases.pageInfo,
933
- data: releases
934
- .filter(release => !!release.tagCommit)
935
- .map(release => {
936
- if (!release.tag || !release.tagCommit) {
937
- this.logger.debug(release);
938
- }
939
- return {
940
- name: release.name || undefined,
941
- tagName: release.tag ? release.tag.name : 'unknown',
942
- sha: release.tagCommit.oid,
943
- notes: release.description,
944
- url: release.url,
945
- draft: release.isDraft,
946
- };
947
- }),
948
- };
413
+ yield* this.gitHubApi.releaseIterator(options);
949
414
  }
950
415
  /**
951
416
  * Iterate through tags with a max number of results scanned.
@@ -1042,33 +507,91 @@ class GitHub {
1042
507
  /**
1043
508
  * Open a pull request
1044
509
  *
1045
- * @deprecated This logic is handled by the Manifest class now as it
1046
- * can be more complicated if the release notes are too big
1047
- * @param {ReleasePullRequest} releasePullRequest Pull request data to update
510
+ * @param {PullRequest} pullRequest Pull request data to update
1048
511
  * @param {string} targetBranch The base branch of the pull request
1049
- * @param {GitHubPR} options The pull request options
512
+ * @param {string} message The commit message for the commit
513
+ * @param {Update[]} updates The files to update
514
+ * @param {CreatePullRequestOptions} options The pull request options
1050
515
  * @throws {GitHubAPIError} on an API error
1051
516
  */
1052
- async createReleasePullRequest(releasePullRequest, targetBranch, options) {
517
+ async createPullRequest(pullRequest, targetBranch, message, updates, options) {
518
+ const changes = await this.buildChangeSet(updates, targetBranch);
519
+ const prNumber = await (0, code_suggester_1.createPullRequest)(this.octokit, changes, {
520
+ upstreamOwner: this.repository.owner,
521
+ upstreamRepo: this.repository.repo,
522
+ title: pullRequest.title,
523
+ branch: pullRequest.headBranchName,
524
+ description: pullRequest.body,
525
+ primary: targetBranch,
526
+ force: true,
527
+ fork: !!(options === null || options === void 0 ? void 0 : options.fork),
528
+ message,
529
+ logger: this.logger,
530
+ draft: !!(options === null || options === void 0 ? void 0 : options.draft),
531
+ labels: pullRequest.labels,
532
+ });
533
+ if (prNumber === 0) {
534
+ this.logger.warn('no code changes detected, skipping pull request creation');
535
+ return {
536
+ headBranchName: pullRequest.headBranchName,
537
+ baseBranchName: targetBranch,
538
+ number: 0,
539
+ title: pullRequest.title,
540
+ body: pullRequest.body,
541
+ labels: pullRequest.labels,
542
+ files: [],
543
+ };
544
+ }
545
+ return await this.getPullRequest(prNumber);
546
+ }
547
+ /**
548
+ * Fetch a pull request given the pull number
549
+ * @param {number} number The pull request number
550
+ * @returns {PullRequest}
551
+ */
552
+ async getPullRequest(number) {
553
+ return await this.gitHubApi.getPullRequest(number);
554
+ }
555
+ /**
556
+ * Update a pull request's title and body.
557
+ * @param {number} number The pull request number
558
+ * @param {ReleasePullRequest} releasePullRequest Pull request data to update
559
+ * @param {string} targetBranch The target branch of the pull request
560
+ * @param {string} options.signoffUser Optional. Commit signoff message
561
+ * @param {boolean} options.fork Optional. Whether to open the pull request from
562
+ * a fork or not. Defaults to `false`
563
+ * @param {PullRequestOverflowHandler} options.pullRequestOverflowHandler Optional.
564
+ * Handles extra large pull request body messages.
565
+ */
566
+ async updatePullRequest(number, releasePullRequest, targetBranch, options) {
567
+ const changes = await this.buildChangeSet(releasePullRequest.updates, targetBranch);
1053
568
  let message = releasePullRequest.title.toString();
1054
569
  if (options === null || options === void 0 ? void 0 : options.signoffUser) {
1055
570
  message = (0, signoff_commit_message_1.signoffCommitMessage)(message, options.signoffUser);
1056
571
  }
1057
- const pullRequestLabels = (options === null || options === void 0 ? void 0 : options.skipLabeling)
1058
- ? []
1059
- : releasePullRequest.labels;
1060
- return await this.createPullRequest({
1061
- headBranchName: releasePullRequest.headRefName,
1062
- baseBranchName: targetBranch,
1063
- number: -1,
1064
- title: releasePullRequest.title.toString(),
1065
- body: releasePullRequest.body.toString().slice(0, MAX_ISSUE_BODY_SIZE),
1066
- labels: pullRequestLabels,
1067
- files: [],
1068
- }, targetBranch, message, releasePullRequest.updates, {
1069
- fork: options === null || options === void 0 ? void 0 : options.fork,
572
+ const title = releasePullRequest.title.toString();
573
+ const body = ((options === null || options === void 0 ? void 0 : options.pullRequestOverflowHandler)
574
+ ? await options.pullRequestOverflowHandler.handleOverflow(releasePullRequest)
575
+ : releasePullRequest.body)
576
+ .toString()
577
+ .slice(0, MAX_ISSUE_BODY_SIZE);
578
+ const prNumber = await (0, code_suggester_1.createPullRequest)(this.octokit, changes, {
579
+ upstreamOwner: this.repository.owner,
580
+ upstreamRepo: this.repository.repo,
581
+ title,
582
+ branch: releasePullRequest.headRefName,
583
+ description: body,
584
+ primary: targetBranch,
585
+ force: true,
586
+ fork: (options === null || options === void 0 ? void 0 : options.fork) === false ? false : true,
587
+ message,
588
+ logger: this.logger,
1070
589
  draft: releasePullRequest.draft,
1071
590
  });
591
+ if (prNumber !== number) {
592
+ this.logger.warn(`updated code for ${prNumber}, but update requested for ${number}`);
593
+ }
594
+ return this.gitHubApi.updatePullRequest(number, title, body);
1072
595
  }
1073
596
  /**
1074
597
  * Given a set of proposed updates, build a changeset to suggest.
@@ -1129,6 +652,45 @@ class GitHub {
1129
652
  async findFilesByExtension(extension, prefix) {
1130
653
  return this.findFilesByExtensionAndRef(extension, this.repository.defaultBranch, prefix);
1131
654
  }
655
+ /**
656
+ * Create a GitHub release
657
+ *
658
+ * @param {Release} release Release parameters
659
+ * @param {ReleaseOptions} options Release option parameters
660
+ * @throws {DuplicateReleaseError} if the release tag already exists
661
+ * @throws {GitHubAPIError} on other API errors
662
+ */
663
+ async createRelease(release, options = {}) {
664
+ return await this.gitHubApi.createRelease(release, options);
665
+ }
666
+ /**
667
+ * Makes a comment on a issue/pull request.
668
+ *
669
+ * @param {string} comment - The body of the comment to post.
670
+ * @param {number} number - The issue or pull request number.
671
+ * @throws {GitHubAPIError} on an API error
672
+ */
673
+ async commentOnIssue(comment, number) {
674
+ return await this.gitHubApi.commentOnIssue(comment, number);
675
+ }
676
+ /**
677
+ * Removes labels from an issue/pull request.
678
+ *
679
+ * @param {string[]} labels The labels to remove.
680
+ * @param {number} number The issue/pull request number.
681
+ */
682
+ async removeIssueLabels(labels, number) {
683
+ return await this.gitHubApi.removeIssueLabels(labels, number);
684
+ }
685
+ /**
686
+ * Adds label to an issue/pull request.
687
+ *
688
+ * @param {string[]} labels The labels to add.
689
+ * @param {number} number The issue/pull request number.
690
+ */
691
+ async addIssueLabels(labels, number) {
692
+ return await this.gitHubApi.addIssueLabels(labels, number);
693
+ }
1132
694
  /**
1133
695
  * Generate release notes from GitHub at tag
1134
696
  * @param {string} tagName Name of new release tag
@@ -1136,14 +698,7 @@ class GitHub {
1136
698
  * @param {string} previousTag Optional. Name of previous tag to analyze commits since
1137
699
  */
1138
700
  async generateReleaseNotes(tagName, targetCommitish, previousTag) {
1139
- const resp = await this.octokit.repos.generateReleaseNotes({
1140
- owner: this.repository.owner,
1141
- repo: this.repository.repo,
1142
- tag_name: tagName,
1143
- previous_tag_name: previousTag,
1144
- target_commitish: targetCommitish,
1145
- });
1146
- return resp.data.body;
701
+ return await this.gitHubApi.generateReleaseNotes(tagName, targetCommitish, previousTag);
1147
702
  }
1148
703
  /**
1149
704
  * Create a single file on a new branch based on an existing
@@ -1156,107 +711,7 @@ class GitHub {
1156
711
  * @returns {string} HTML URL of the new file
1157
712
  */
1158
713
  async createFileOnNewBranch(filename, contents, newBranchName, baseBranchName) {
1159
- // create or update new branch to match base branch
1160
- await this.forkBranch(newBranchName, baseBranchName);
1161
- // use the single file upload API
1162
- const { data: { content }, } = await this.octokit.repos.createOrUpdateFileContents({
1163
- owner: this.repository.owner,
1164
- repo: this.repository.repo,
1165
- path: filename,
1166
- // contents need to be base64 encoded
1167
- content: Buffer.from(contents, 'binary').toString('base64'),
1168
- message: 'Saving release notes',
1169
- branch: newBranchName,
1170
- });
1171
- if (!(content === null || content === void 0 ? void 0 : content.html_url)) {
1172
- throw new Error(`Failed to write to file: ${filename} on branch: ${newBranchName}`);
1173
- }
1174
- return content.html_url;
1175
- }
1176
- /**
1177
- * Helper to fetch the SHA of a branch
1178
- * @param {string} branchName The name of the branch
1179
- * @return {string | undefined} Returns the SHA of the branch
1180
- * or undefined if it can't be found.
1181
- */
1182
- async getBranchSha(branchName) {
1183
- this.logger.debug(`Looking up SHA for branch: ${branchName}`);
1184
- try {
1185
- const { data: { object: { sha }, }, } = await this.octokit.git.getRef({
1186
- owner: this.repository.owner,
1187
- repo: this.repository.repo,
1188
- ref: `heads/${branchName}`,
1189
- });
1190
- this.logger.debug(`SHA for branch: ${sha}`);
1191
- return sha;
1192
- }
1193
- catch (e) {
1194
- if (e instanceof request_error_1.RequestError && e.status === 404) {
1195
- this.logger.debug(`Branch: ${branchName} does not exist`);
1196
- return undefined;
1197
- }
1198
- throw e;
1199
- }
1200
- }
1201
- /**
1202
- * Helper to fork a branch from an existing branch. Uses `force` so
1203
- * it will overwrite the contents of `targetBranchName` to match
1204
- * the current contents of `baseBranchName`.
1205
- *
1206
- * @param {string} targetBranchName The name of the new forked branch
1207
- * @param {string} baseBranchName The base branch from which to fork.
1208
- * @returns {string} The branch SHA
1209
- * @throws {ConfigurationError} if the base branch cannot be found.
1210
- */
1211
- async forkBranch(targetBranchName, baseBranchName) {
1212
- const baseBranchSha = await this.getBranchSha(baseBranchName);
1213
- if (!baseBranchSha) {
1214
- // this is highly unlikely to be thrown as we will have
1215
- // already attempted to read from the branch
1216
- throw new errors_1.ConfigurationError(`Unable to find base branch: ${baseBranchName}`, 'core', `${this.repository.owner}/${this.repository.repo}`);
1217
- }
1218
- // see if newBranchName exists
1219
- if (await this.getBranchSha(targetBranchName)) {
1220
- // branch already exists, update it to the match the base branch
1221
- const branchSha = await this.updateBranchSha(targetBranchName, baseBranchSha);
1222
- this.logger.debug(`Updated ${targetBranchName} to match ${baseBranchName} at ${branchSha}`);
1223
- return branchSha;
1224
- }
1225
- else {
1226
- // branch does not exist, create a new branch from the base branch
1227
- const branchSha = await this.createNewBranch(targetBranchName, baseBranchSha);
1228
- this.logger.debug(`Forked ${targetBranchName} from ${baseBranchName} at ${branchSha}`);
1229
- return branchSha;
1230
- }
1231
- }
1232
- /**
1233
- * Helper to create a new branch from a given SHA.
1234
- * @param {string} branchName The new branch name
1235
- * @param {string} branchSha The SHA of the branch
1236
- * @returns {string} The SHA of the new branch
1237
- */
1238
- async createNewBranch(branchName, branchSha) {
1239
- this.logger.debug(`Creating new branch: ${branchName} at ${branchSha}`);
1240
- const { data: { object: { sha }, }, } = await this.octokit.git.createRef({
1241
- owner: this.repository.owner,
1242
- repo: this.repository.repo,
1243
- ref: `refs/heads/${branchName}`,
1244
- sha: branchSha,
1245
- });
1246
- this.logger.debug(`New branch: ${branchName} at ${sha}`);
1247
- return sha;
1248
- }
1249
- async updateBranchSha(branchName, branchSha) {
1250
- this.logger.debug(`Updating branch ${branchName} to ${branchSha}`);
1251
- const { data: { object: { sha }, }, } = await this.octokit.git.updateRef({
1252
- owner: this.repository.owner,
1253
- repo: this.repository.repo,
1254
- ref: `heads/${branchName}`,
1255
- sha: branchSha,
1256
- force: true,
1257
- });
1258
- this.logger.debug(`Updated branch: ${branchName} to ${sha}`);
1259
- return sha;
714
+ return await this.gitHubApi.createFileOnNewBranch(filename, contents, newBranchName, baseBranchName);
1260
715
  }
1261
716
  }
1262
717
  exports.GitHub = GitHub;