release-please 17.4.0 → 17.5.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 (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 -687
  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 +776 -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 +3 -3
@@ -0,0 +1,701 @@
1
+ "use strict";
2
+ // Copyright 2026 Google LLC
3
+ //
4
+ // Licensed under the Apache License, Version 2.0 (the "License");
5
+ // you may not use this file except in compliance with the License.
6
+ // You may obtain a copy of the License at
7
+ //
8
+ // http://www.apache.org/licenses/LICENSE-2.0
9
+ //
10
+ // Unless required by applicable law or agreed to in writing, software
11
+ // distributed under the License is distributed on an "AS IS" BASIS,
12
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ // See the License for the specific language governing permissions and
14
+ // limitations under the License.
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.sleepInMs = exports.wrapAsync = exports.GitHubApi = exports.MAX_ISSUE_BODY_SIZE = exports.MAX_SLEEP_SECONDS = exports.GH_GRAPHQL_URL = exports.GH_API_URL = void 0;
17
+ const rest_1 = require("@octokit/rest");
18
+ const request_1 = require("@octokit/request");
19
+ const request_error_1 = require("@octokit/request-error");
20
+ const errors_1 = require("./errors");
21
+ const logger_1 = require("./util/logger");
22
+ const graphql_1 = require("@octokit/graphql");
23
+ const https_proxy_agent_1 = require("https-proxy-agent");
24
+ const http_proxy_agent_1 = require("http-proxy-agent");
25
+ exports.GH_API_URL = 'https://api.github.com';
26
+ exports.GH_GRAPHQL_URL = 'https://api.github.com';
27
+ exports.MAX_SLEEP_SECONDS = 20;
28
+ exports.MAX_ISSUE_BODY_SIZE = 65536;
29
+ class GitHubApi {
30
+ constructor(options) {
31
+ var _a;
32
+ this.graphqlRequest = (0, exports.wrapAsync)(async (opts, options) => {
33
+ var _a;
34
+ let maxRetries = (_a = options === null || options === void 0 ? void 0 : options.maxRetries) !== null && _a !== void 0 ? _a : 5;
35
+ let seconds = 1;
36
+ while (maxRetries >= 0) {
37
+ try {
38
+ const response = await this.graphql(opts);
39
+ if (response) {
40
+ return response;
41
+ }
42
+ this.logger.trace('no GraphQL response, retrying');
43
+ }
44
+ catch (err) {
45
+ if (err.status !== 502) {
46
+ throw err;
47
+ }
48
+ if (maxRetries === 0) {
49
+ this.logger.warn('ran out of retries and response is required');
50
+ throw err;
51
+ }
52
+ this.logger.info(`received 502 error, ${maxRetries} attempts remaining`);
53
+ }
54
+ maxRetries -= 1;
55
+ if (maxRetries >= 0) {
56
+ this.logger.trace(`sleeping ${seconds} seconds`);
57
+ await (0, exports.sleepInMs)(1000 * seconds);
58
+ seconds = Math.min(seconds * 2, exports.MAX_SLEEP_SECONDS);
59
+ }
60
+ }
61
+ this.logger.trace('ran out of retries');
62
+ return undefined;
63
+ });
64
+ this.createPullRequest = (0, exports.wrapAsync)(async (pullRequest, targetBranch, options) => {
65
+ const pullResponseData = (await this.octokit.pulls.create({
66
+ owner: this.repository.owner,
67
+ repo: this.repository.repo,
68
+ title: pullRequest.title,
69
+ head: `${this.repository.owner}:${pullRequest.headBranchName}`,
70
+ base: targetBranch,
71
+ body: pullRequest.body,
72
+ maintainer_can_modify: true,
73
+ draft: !!(options === null || options === void 0 ? void 0 : options.draft),
74
+ })).data;
75
+ this.logger.info(`Successfully opened pull request available at url: ${pullResponseData.html_url}.`);
76
+ return await this.getPullRequest(pullResponseData.number);
77
+ });
78
+ /**
79
+ * Fetch a pull request given the pull number
80
+ * @param {number} number The pull request number
81
+ * @returns {PullRequest}
82
+ */
83
+ this.getPullRequest = (0, exports.wrapAsync)(async (number) => {
84
+ const response = await this.octokit.pulls.get({
85
+ owner: this.repository.owner,
86
+ repo: this.repository.repo,
87
+ pull_number: number,
88
+ });
89
+ return {
90
+ headBranchName: response.data.head.ref,
91
+ baseBranchName: response.data.base.ref,
92
+ number: response.data.number,
93
+ title: response.data.title,
94
+ body: response.data.body || '',
95
+ files: [],
96
+ labels: response.data.labels
97
+ .map((label) => label.name)
98
+ .filter((name) => !!name),
99
+ };
100
+ });
101
+ this.updatePullRequest = (0, exports.wrapAsync)(async (number, title, body) => {
102
+ const response = await this.octokit.pulls.update({
103
+ owner: this.repository.owner,
104
+ repo: this.repository.repo,
105
+ pull_number: number,
106
+ title,
107
+ body,
108
+ state: 'open',
109
+ });
110
+ return {
111
+ headBranchName: response.data.head.ref,
112
+ baseBranchName: response.data.base.ref,
113
+ number: response.data.number,
114
+ title: response.data.title,
115
+ body: response.data.body || '',
116
+ files: [],
117
+ labels: response.data.labels
118
+ .map((label) => label.name)
119
+ .filter((name) => !!name),
120
+ };
121
+ });
122
+ /**
123
+ * Create a GitHub release
124
+ *
125
+ * @param {Release} release Release parameters
126
+ * @param {ScmReleaseOptions} options Release option parameters
127
+ * @throws {DuplicateReleaseError} if the release tag already exists
128
+ * @throws {GitHubAPIError} on other API errors
129
+ */
130
+ this.createRelease = (0, exports.wrapAsync)(async (release, options = {}) => {
131
+ if (options.forceTag) {
132
+ try {
133
+ await this.octokit.git.createRef({
134
+ owner: this.repository.owner,
135
+ repo: this.repository.repo,
136
+ ref: `refs/tags/${release.tag.toString()}`,
137
+ sha: release.sha,
138
+ });
139
+ }
140
+ catch (err) {
141
+ // ignore if tag already exists
142
+ if (err.status === 422) {
143
+ this.logger.debug(`Tag ${release.tag.toString()} already exists, skipping tag creation`);
144
+ }
145
+ else {
146
+ throw err;
147
+ }
148
+ }
149
+ }
150
+ const resp = await this.octokit.repos.createRelease({
151
+ name: release.name,
152
+ owner: this.repository.owner,
153
+ repo: this.repository.repo,
154
+ tag_name: release.tag.toString(),
155
+ body: release.notes,
156
+ draft: !!options.draft,
157
+ prerelease: !!options.prerelease,
158
+ target_commitish: release.sha,
159
+ });
160
+ return {
161
+ id: resp.data.id,
162
+ name: resp.data.name || undefined,
163
+ tagName: resp.data.tag_name,
164
+ sha: resp.data.target_commitish,
165
+ notes: resp.data.body_text ||
166
+ resp.data.body ||
167
+ resp.data.body_html ||
168
+ undefined,
169
+ url: resp.data.html_url,
170
+ draft: resp.data.draft,
171
+ uploadUrl: resp.data.upload_url,
172
+ };
173
+ }, e => {
174
+ if (e instanceof request_error_1.RequestError) {
175
+ if (e.status === 422 &&
176
+ errors_1.GitHubAPIError.parseErrors(e).some(error => {
177
+ return error.code === 'already_exists';
178
+ })) {
179
+ throw new errors_1.DuplicateReleaseError(e, 'tagName');
180
+ }
181
+ }
182
+ });
183
+ /**
184
+ * Makes a comment on a issue/pull request.
185
+ *
186
+ * @param {string} comment - The body of the comment to post.
187
+ * @param {number} number - The issue or pull request number.
188
+ * @throws {GitHubAPIError} on an API error
189
+ */
190
+ this.commentOnIssue = (0, exports.wrapAsync)(async (comment, number) => {
191
+ this.logger.debug(`adding comment to https://github.com/${this.repository.owner}/${this.repository.repo}/issues/${number}`);
192
+ const resp = await this.octokit.issues.createComment({
193
+ owner: this.repository.owner,
194
+ repo: this.repository.repo,
195
+ issue_number: number,
196
+ body: comment,
197
+ });
198
+ return resp.data.html_url;
199
+ });
200
+ /**
201
+ * Removes labels from an issue/pull request.
202
+ *
203
+ * @param {string[]} labels The labels to remove.
204
+ * @param {number} number The issue/pull request number.
205
+ */
206
+ this.removeIssueLabels = (0, exports.wrapAsync)(async (labels, number) => {
207
+ if (labels.length === 0) {
208
+ return;
209
+ }
210
+ this.logger.debug(`removing labels: ${labels} from issue/pull ${number}`);
211
+ await Promise.all(labels.map(label => this.octokit.issues.removeLabel({
212
+ owner: this.repository.owner,
213
+ repo: this.repository.repo,
214
+ issue_number: number,
215
+ name: label,
216
+ })));
217
+ });
218
+ /**
219
+ * Adds label to an issue/pull request.
220
+ *
221
+ * @param {string[]} labels The labels to add.
222
+ * @param {number} number The issue/pull request number.
223
+ */
224
+ this.addIssueLabels = (0, exports.wrapAsync)(async (labels, number) => {
225
+ if (labels.length === 0) {
226
+ return;
227
+ }
228
+ this.logger.debug(`adding labels: ${labels} from issue/pull ${number}`);
229
+ await this.octokit.issues.addLabels({
230
+ owner: this.repository.owner,
231
+ repo: this.repository.repo,
232
+ issue_number: number,
233
+ labels,
234
+ });
235
+ });
236
+ this.repository = options.repository;
237
+ this.octokitAPIs = options.octokitAPIs;
238
+ this.octokit = options.octokitAPIs.octokit;
239
+ this.graphql = options.octokitAPIs.graphql;
240
+ this.logger = (_a = options.logger) !== null && _a !== void 0 ? _a : logger_1.logger;
241
+ }
242
+ static createDefaultAgent(baseUrl, defaultProxy) {
243
+ if (!defaultProxy) {
244
+ return undefined;
245
+ }
246
+ const { host, port } = defaultProxy;
247
+ if (new URL(baseUrl).protocol.replace(':', '') === 'http') {
248
+ return new http_proxy_agent_1.HttpProxyAgent(`http://${host}:${port}`);
249
+ }
250
+ else {
251
+ return new https_proxy_agent_1.HttpsProxyAgent(`https://${host}:${port}`);
252
+ }
253
+ }
254
+ static async create(options) {
255
+ var _a, _b, _c, _d;
256
+ const apiUrl = (_a = options.apiUrl) !== null && _a !== void 0 ? _a : exports.GH_API_URL;
257
+ const graphqlUrl = (_b = options.graphqlUrl) !== null && _b !== void 0 ? _b : exports.GH_GRAPHQL_URL;
258
+ const releasePleaseVersion = require('../../package.json').version;
259
+ const apis = (_c = options.octokitAPIs) !== null && _c !== void 0 ? _c : {
260
+ octokit: new rest_1.Octokit({
261
+ baseUrl: apiUrl,
262
+ auth: options.token,
263
+ request: {
264
+ agent: this.createDefaultAgent(apiUrl, options.proxy),
265
+ fetch: options.fetch,
266
+ },
267
+ }),
268
+ request: request_1.request.defaults({
269
+ baseUrl: apiUrl,
270
+ headers: {
271
+ 'user-agent': `release-please/${releasePleaseVersion}`,
272
+ Authorization: `token ${options.token}`,
273
+ },
274
+ fetch: options.fetch,
275
+ }),
276
+ graphql: graphql_1.graphql.defaults({
277
+ baseUrl: graphqlUrl,
278
+ request: {
279
+ agent: this.createDefaultAgent(graphqlUrl, options.proxy),
280
+ fetch: options.fetch,
281
+ },
282
+ headers: {
283
+ 'user-agent': `release-please/${releasePleaseVersion}`,
284
+ Authorization: `token ${options.token}`,
285
+ 'content-type': 'application/vnd.github.v3+json',
286
+ },
287
+ }),
288
+ };
289
+ const opts = {
290
+ repository: {
291
+ owner: options.owner,
292
+ repo: options.repo,
293
+ defaultBranch: (_d = options.defaultBranch) !== null && _d !== void 0 ? _d : (await GitHubApi.defaultBranch(options.owner, options.repo, apis.octokit)),
294
+ },
295
+ octokitAPIs: apis,
296
+ logger: options.logger,
297
+ };
298
+ return new GitHubApi(opts);
299
+ }
300
+ static async defaultBranch(owner, repo, octokit) {
301
+ const { data } = await octokit.repos.get({
302
+ repo,
303
+ owner,
304
+ });
305
+ return data.default_branch;
306
+ }
307
+ /**
308
+ * Iterate through merged pull requests with a max number of results scanned.
309
+ *
310
+ * @param {string} targetBranch Target branch of commit.
311
+ * @param {string} status The status of the pull request. Defaults to 'MERGED'.
312
+ * @param {number} maxResults Limit the number of results searched. Defaults to
313
+ * unlimited.
314
+ * @param {boolean} includeFiles Whether to fetch the list of files included in
315
+ * the pull request. Defaults to `true`.
316
+ * @yields {PullRequest}
317
+ * @throws {GitHubAPIError} on an API error
318
+ */
319
+ async *pullRequestIterator(targetBranch, status = 'MERGED', maxResults = Number.MAX_SAFE_INTEGER, includeFiles = true) {
320
+ const generator = includeFiles
321
+ ? this.pullRequestIteratorWithFiles(targetBranch, status, maxResults)
322
+ : this.pullRequestIteratorWithoutFiles(targetBranch, status, maxResults);
323
+ for await (const pullRequest of generator) {
324
+ yield pullRequest;
325
+ }
326
+ }
327
+ /**
328
+ * Helper implementation of pullRequestIterator that includes files via
329
+ * the graphQL API.
330
+ *
331
+ * @param {string} targetBranch The base branch of the pull request
332
+ * @param {string} status The status of the pull request
333
+ * @param {number} maxResults Limit the number of results searched
334
+ */
335
+ async *pullRequestIteratorWithFiles(targetBranch, status = 'MERGED', maxResults = Number.MAX_SAFE_INTEGER) {
336
+ let cursor = undefined;
337
+ let results = 0;
338
+ while (results < maxResults) {
339
+ const response = await this.pullRequestsGraphQL(targetBranch, status, cursor);
340
+ // no response usually means we ran out of results
341
+ if (!response) {
342
+ break;
343
+ }
344
+ for (let i = 0; i < response.data.length; i++) {
345
+ results += 1;
346
+ yield response.data[i];
347
+ }
348
+ if (!response.pageInfo.hasNextPage) {
349
+ break;
350
+ }
351
+ cursor = response.pageInfo.endCursor;
352
+ }
353
+ }
354
+ /**
355
+ * Helper implementation of pullRequestIterator that excludes files
356
+ * via the REST API.
357
+ *
358
+ * @param {string} targetBranch The base branch of the pull request
359
+ * @param {string} status The status of the pull request
360
+ * @param {number} maxResults Limit the number of results searched
361
+ */
362
+ async *pullRequestIteratorWithoutFiles(targetBranch, status = 'MERGED', maxResults = Number.MAX_SAFE_INTEGER) {
363
+ const statusMap = {
364
+ OPEN: 'open',
365
+ CLOSED: 'closed',
366
+ MERGED: 'closed',
367
+ };
368
+ let results = 0;
369
+ for await (const { data: pulls } of this.octokit.paginate.iterator('GET /repos/{owner}/{repo}/pulls', {
370
+ state: statusMap[status],
371
+ owner: this.repository.owner,
372
+ repo: this.repository.repo,
373
+ base: targetBranch,
374
+ sort: 'updated',
375
+ direction: 'desc',
376
+ })) {
377
+ for (const pull of pulls) {
378
+ // The REST API does not have an option for "merged"
379
+ // pull requests - they are closed with a `merged_at` timestamp
380
+ if (status !== 'MERGED' || pull.merged_at) {
381
+ results += 1;
382
+ yield {
383
+ headBranchName: pull.head.ref,
384
+ baseBranchName: pull.base.ref,
385
+ number: pull.number,
386
+ title: pull.title,
387
+ body: pull.body || '',
388
+ labels: pull.labels.map((label) => label.name),
389
+ files: [],
390
+ sha: pull.merge_commit_sha || undefined,
391
+ };
392
+ if (results >= maxResults) {
393
+ break;
394
+ }
395
+ }
396
+ }
397
+ if (results >= maxResults) {
398
+ break;
399
+ }
400
+ }
401
+ }
402
+ /**
403
+ * Return a list of merged pull requests. The list is not guaranteed to be sorted
404
+ * by merged_at, but is generally most recent first.
405
+ *
406
+ * @param {string} targetBranch - Base branch of the pull request. Defaults to
407
+ * the configured default branch.
408
+ * @param {number} page - Page of results. Defaults to 1.
409
+ * @param {number} perPage - Number of results per page. Defaults to 100.
410
+ * @returns {PullRequestHistory | null} - List of merged pull requests
411
+ * @throws {GitHubAPIError} on an API error
412
+ */
413
+ async pullRequestsGraphQL(targetBranch, states = 'MERGED', cursor) {
414
+ var _a;
415
+ this.logger.debug(`Fetching ${states} pull requests on branch ${targetBranch} with cursor ${cursor}`);
416
+ const response = await this.graphqlRequest({
417
+ query: `query mergedPullRequests($owner: String!, $repo: String!, $num: Int!, $maxFilesChanged: Int, $targetBranch: String!, $states: [PullRequestState!], $cursor: String) {
418
+ repository(owner: $owner, name: $repo) {
419
+ pullRequests(first: $num, after: $cursor, baseRefName: $targetBranch, states: $states, orderBy: {field: CREATED_AT, direction: DESC}) {
420
+ nodes {
421
+ number
422
+ title
423
+ baseRefName
424
+ headRefName
425
+ labels(first: 10) {
426
+ nodes {
427
+ name
428
+ }
429
+ }
430
+ body
431
+ mergeCommit {
432
+ oid
433
+ }
434
+ files(first: $maxFilesChanged) {
435
+ nodes {
436
+ path
437
+ }
438
+ pageInfo {
439
+ endCursor
440
+ hasNextPage
441
+ }
442
+ }
443
+ }
444
+ pageInfo {
445
+ endCursor
446
+ hasNextPage
447
+ }
448
+ }
449
+ }
450
+ }`,
451
+ cursor,
452
+ owner: this.repository.owner,
453
+ repo: this.repository.repo,
454
+ num: 25,
455
+ targetBranch,
456
+ states,
457
+ maxFilesChanged: 64,
458
+ });
459
+ if (!((_a = response === null || response === void 0 ? void 0 : response.repository) === null || _a === void 0 ? void 0 : _a.pullRequests)) {
460
+ this.logger.warn(`Could not find merged pull requests for branch ${targetBranch} - it likely does not exist.`);
461
+ return null;
462
+ }
463
+ const pullRequests = (response.repository.pullRequests.nodes ||
464
+ []);
465
+ return {
466
+ pageInfo: response.repository.pullRequests.pageInfo,
467
+ data: pullRequests.map(pullRequest => {
468
+ var _a, _b, _c;
469
+ return {
470
+ sha: (_a = pullRequest.mergeCommit) === null || _a === void 0 ? void 0 : _a.oid,
471
+ number: pullRequest.number,
472
+ baseBranchName: pullRequest.baseRefName,
473
+ headBranchName: pullRequest.headRefName,
474
+ labels: (((_b = pullRequest.labels) === null || _b === void 0 ? void 0 : _b.nodes) || []).map(l => l.name),
475
+ title: pullRequest.title,
476
+ body: pullRequest.body + '',
477
+ files: (((_c = pullRequest.files) === null || _c === void 0 ? void 0 : _c.nodes) || []).map(node => node.path),
478
+ };
479
+ }),
480
+ };
481
+ }
482
+ /**
483
+ * Iterate through releases with a max number of results scanned.
484
+ *
485
+ * @param {ReleaseIteratorOptions} options Query options
486
+ * @param {number} options.maxResults Limit the number of results scanned.
487
+ * Defaults to unlimited.
488
+ * @yields {ScmRelease}
489
+ * @throws {GitHubAPIError} on an API error
490
+ */
491
+ async *releaseIterator(options = {}) {
492
+ var _a;
493
+ const maxResults = (_a = options.maxResults) !== null && _a !== void 0 ? _a : Number.MAX_SAFE_INTEGER;
494
+ let results = 0;
495
+ let cursor = undefined;
496
+ while (true) {
497
+ const response = await this.releaseGraphQL(cursor);
498
+ if (!response) {
499
+ break;
500
+ }
501
+ for (let i = 0; i < response.data.length; i++) {
502
+ if ((results += 1) > maxResults) {
503
+ break;
504
+ }
505
+ yield response.data[i];
506
+ }
507
+ if (results > maxResults || !response.pageInfo.hasNextPage) {
508
+ break;
509
+ }
510
+ cursor = response.pageInfo.endCursor;
511
+ }
512
+ }
513
+ async releaseGraphQL(cursor) {
514
+ var _a, _b, _c;
515
+ this.logger.debug(`Fetching releases with cursor ${cursor}`);
516
+ const response = await this.graphqlRequest({
517
+ query: `query releases($owner: String!, $repo: String!, $num: Int!, $cursor: String) {
518
+ repository(owner: $owner, name: $repo) {
519
+ releases(first: $num, after: $cursor, orderBy: {field: CREATED_AT, direction: DESC}) {
520
+ nodes {
521
+ name
522
+ tag {
523
+ name
524
+ }
525
+ tagCommit {
526
+ oid
527
+ }
528
+ url
529
+ description
530
+ isDraft
531
+ }
532
+ pageInfo {
533
+ endCursor
534
+ hasNextPage
535
+ }
536
+ }
537
+ }
538
+ }`,
539
+ cursor,
540
+ owner: this.repository.owner,
541
+ repo: this.repository.repo,
542
+ num: 25,
543
+ });
544
+ if (!((_c = (_b = (_a = response === null || response === void 0 ? void 0 : response.repository) === null || _a === void 0 ? void 0 : _a.releases) === null || _b === void 0 ? void 0 : _b.nodes) === null || _c === void 0 ? void 0 : _c.length)) {
545
+ this.logger.warn('Could not find releases.');
546
+ return null;
547
+ }
548
+ const releases = response.repository.releases.nodes;
549
+ return {
550
+ pageInfo: response.repository.releases.pageInfo,
551
+ data: releases
552
+ .filter(release => !!release.tagCommit)
553
+ .map(release => {
554
+ if (!release.tag || !release.tagCommit) {
555
+ this.logger.debug(release);
556
+ }
557
+ return {
558
+ name: release.name || undefined,
559
+ tagName: release.tag ? release.tag.name : 'unknown',
560
+ sha: release.tagCommit.oid,
561
+ notes: release.description,
562
+ url: release.url,
563
+ draft: release.isDraft,
564
+ };
565
+ }),
566
+ };
567
+ }
568
+ /**
569
+ * Generate release notes from GitHub at tag
570
+ * @param {string} tagName Name of new release tag
571
+ * @param {string} targetCommitish Target commitish for new tag
572
+ * @param {string} previousTag Optional. Name of previous tag to analyze commits since
573
+ */
574
+ async generateReleaseNotes(tagName, targetCommitish, previousTag) {
575
+ const resp = await this.octokit.repos.generateReleaseNotes({
576
+ owner: this.repository.owner,
577
+ repo: this.repository.repo,
578
+ tag_name: tagName,
579
+ previous_tag_name: previousTag,
580
+ target_commitish: targetCommitish,
581
+ });
582
+ return resp.data.body;
583
+ }
584
+ /**
585
+ * Create a single file on a new branch based on an existing
586
+ * branch. This will force-push to that branch.
587
+ * @param {string} filename Filename with path in the repository
588
+ * @param {string} contents Contents of the file
589
+ * @param {string} newBranchName Name of the new branch
590
+ * @param {string} baseBranchName Name of the base branch (where
591
+ * new branch is forked from)
592
+ * @returns {string} HTML URL of the new file
593
+ */
594
+ async createFileOnNewBranch(filename, contents, newBranchName, baseBranchName) {
595
+ // create or update new branch to match base branch
596
+ await this.forkBranch(newBranchName, baseBranchName);
597
+ // use the single file upload API
598
+ const { data: { content }, } = await this.octokit.repos.createOrUpdateFileContents({
599
+ owner: this.repository.owner,
600
+ repo: this.repository.repo,
601
+ path: filename,
602
+ // contents need to be base64 encoded
603
+ content: Buffer.from(contents, 'binary').toString('base64'),
604
+ message: 'Saving release notes',
605
+ branch: newBranchName,
606
+ });
607
+ return (content === null || content === void 0 ? void 0 : content.html_url) || '';
608
+ }
609
+ /**
610
+ * Fork a branch from a base branch.
611
+ */
612
+ async forkBranch(targetBranchName, baseBranchName) {
613
+ const baseBranchSha = await this.getBranchSha(baseBranchName);
614
+ if (!baseBranchSha) {
615
+ throw new errors_1.ConfigurationError(`Unable to find base branch: ${baseBranchName}`, 'core', `${this.repository.owner}/${this.repository.repo}`);
616
+ }
617
+ if (await this.getBranchSha(targetBranchName)) {
618
+ const branchSha = await this.updateBranchSha(targetBranchName, baseBranchSha);
619
+ this.logger.debug(`Updated ${targetBranchName} to match ${baseBranchName} at ${branchSha}`);
620
+ return branchSha;
621
+ }
622
+ else {
623
+ const branchSha = await this.createNewBranch(targetBranchName, baseBranchSha);
624
+ this.logger.debug(`Created ${targetBranchName} from ${baseBranchName} at ${branchSha}`);
625
+ return branchSha;
626
+ }
627
+ }
628
+ /**
629
+ * Helper to fetch the SHA of a branch
630
+ */
631
+ async getBranchSha(branchName) {
632
+ this.logger.debug(`Looking up SHA for branch: ${branchName}`);
633
+ try {
634
+ const { data: { object: { sha }, }, } = await this.octokit.git.getRef({
635
+ owner: this.repository.owner,
636
+ repo: this.repository.repo,
637
+ ref: `heads/${branchName}`,
638
+ });
639
+ this.logger.debug(`SHA for branch: ${sha}`);
640
+ return sha;
641
+ }
642
+ catch (e) {
643
+ if (e instanceof request_error_1.RequestError && e.status === 404) {
644
+ this.logger.debug(`Branch: ${branchName} does not exist`);
645
+ return undefined;
646
+ }
647
+ throw e;
648
+ }
649
+ }
650
+ /**
651
+ * Helper to create a new branch from a given SHA.
652
+ */
653
+ async createNewBranch(branchName, branchSha) {
654
+ this.logger.debug(`Creating new branch: ${branchName} at ${branchSha}`);
655
+ const { data: { object: { sha }, }, } = await this.octokit.git.createRef({
656
+ owner: this.repository.owner,
657
+ repo: this.repository.repo,
658
+ ref: `refs/heads/${branchName}`,
659
+ sha: branchSha,
660
+ });
661
+ this.logger.debug(`New branch: ${branchName} at ${sha}`);
662
+ return sha;
663
+ }
664
+ /**
665
+ * Helper to update branch SHA.
666
+ */
667
+ async updateBranchSha(branchName, branchSha) {
668
+ this.logger.debug(`Updating branch ${branchName} to ${branchSha}`);
669
+ const { data: { object: { sha }, }, } = await this.octokit.git.updateRef({
670
+ owner: this.repository.owner,
671
+ repo: this.repository.repo,
672
+ ref: `heads/${branchName}`,
673
+ sha: branchSha,
674
+ force: true,
675
+ });
676
+ this.logger.debug(`Updated branch: ${branchName} to ${sha}`);
677
+ return sha;
678
+ }
679
+ }
680
+ exports.GitHubApi = GitHubApi;
681
+ /* eslint-disable @typescript-eslint/no-explicit-any */
682
+ const wrapAsync = (fn, errorHandler) => {
683
+ return async (...args) => {
684
+ try {
685
+ return await fn(...args);
686
+ }
687
+ catch (e) {
688
+ if (errorHandler) {
689
+ errorHandler(e);
690
+ }
691
+ if (e instanceof request_error_1.RequestError) {
692
+ throw new errors_1.GitHubAPIError(e);
693
+ }
694
+ throw e;
695
+ }
696
+ };
697
+ };
698
+ exports.wrapAsync = wrapAsync;
699
+ const sleepInMs = (ms) => new Promise(resolve => setTimeout(resolve, ms));
700
+ exports.sleepInMs = sleepInMs;
701
+ //# sourceMappingURL=github-api.js.map