release-please 12.3.0 → 13.0.0-candidate.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 (244) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +5 -0
  3. package/build/src/bin/release-please.d.ts +51 -11
  4. package/build/src/bin/release-please.js +409 -150
  5. package/build/src/bootstrapper.d.ts +12 -0
  6. package/build/src/bootstrapper.js +60 -0
  7. package/build/src/commit.d.ts +26 -0
  8. package/build/src/{util/to-conventional-changelog-format.js → commit.js} +97 -2
  9. package/build/src/errors/index.d.ts +0 -5
  10. package/build/src/errors/index.js +1 -10
  11. package/build/src/factory.d.ts +25 -37
  12. package/build/src/factory.js +159 -150
  13. package/build/src/github.d.ts +151 -883
  14. package/build/src/github.js +584 -1013
  15. package/build/src/manifest.d.ts +177 -48
  16. package/build/src/manifest.js +583 -487
  17. package/build/src/plugin.d.ts +20 -0
  18. package/build/src/{plugins/plugin.js → plugin.js} +10 -9
  19. package/build/src/plugins/cargo-workspace.d.ts +48 -18
  20. package/build/src/plugins/cargo-workspace.js +247 -328
  21. package/build/src/plugins/merge.d.ts +11 -0
  22. package/build/src/plugins/merge.js +83 -0
  23. package/build/src/plugins/node-workspace.d.ts +35 -17
  24. package/build/src/plugins/node-workspace.js +234 -271
  25. package/build/src/plugins/workspace.d.ts +102 -0
  26. package/build/src/plugins/workspace.js +170 -0
  27. package/build/src/pull-request.d.ts +10 -0
  28. package/build/src/{updaters/java/readme.js → pull-request.js} +2 -7
  29. package/build/src/release-notes.d.ts +29 -0
  30. package/build/src/release-notes.js +71 -0
  31. package/build/src/release-pull-request.d.ts +13 -0
  32. package/build/src/{updaters/java/pom-xml.js → release-pull-request.js} +2 -7
  33. package/build/src/release.d.ts +6 -0
  34. package/build/src/release.js +16 -0
  35. package/build/src/repository.d.ts +5 -0
  36. package/build/src/repository.js +16 -0
  37. package/build/src/strategies/dart.d.ts +8 -0
  38. package/build/src/strategies/dart.js +63 -0
  39. package/build/src/strategies/elixir.d.ts +5 -0
  40. package/build/src/{releasers → strategies}/elixir.js +18 -14
  41. package/build/src/strategies/go-yoshi.d.ts +13 -0
  42. package/build/src/strategies/go-yoshi.js +106 -0
  43. package/build/src/strategies/go.d.ts +5 -0
  44. package/build/src/{releasers → strategies}/go.js +11 -12
  45. package/build/src/strategies/helm.d.ts +8 -0
  46. package/build/src/strategies/helm.js +63 -0
  47. package/build/src/strategies/java-yoshi.d.ts +24 -0
  48. package/build/src/strategies/java-yoshi.js +203 -0
  49. package/build/src/strategies/krm-blueprint.d.ts +7 -0
  50. package/build/src/{releasers → strategies}/krm-blueprint.js +26 -22
  51. package/build/src/strategies/node.d.ts +9 -0
  52. package/build/src/strategies/node.js +82 -0
  53. package/build/src/strategies/ocaml.d.ts +5 -0
  54. package/build/src/{releasers → strategies}/ocaml.js +34 -28
  55. package/build/src/strategies/php-yoshi.d.ts +9 -0
  56. package/build/src/strategies/php-yoshi.js +214 -0
  57. package/build/src/strategies/php.d.ts +6 -0
  58. package/build/src/{releasers → strategies}/php.js +24 -23
  59. package/build/src/strategies/python.d.ts +8 -0
  60. package/build/src/strategies/python.js +117 -0
  61. package/build/src/strategies/ruby-yoshi.d.ts +17 -0
  62. package/build/src/strategies/ruby-yoshi.js +116 -0
  63. package/build/src/strategies/ruby.d.ts +13 -0
  64. package/build/src/{releasers → strategies}/ruby.js +26 -27
  65. package/build/src/strategies/rust.d.ts +20 -0
  66. package/build/src/strategies/rust.js +120 -0
  67. package/build/src/strategies/simple.d.ts +5 -0
  68. package/build/src/{releasers → strategies}/simple.js +18 -14
  69. package/build/src/strategies/terraform-module.d.ts +7 -0
  70. package/build/src/{releasers → strategies}/terraform-module.js +29 -23
  71. package/build/src/strategy.d.ts +99 -0
  72. package/build/src/strategy.js +237 -0
  73. package/build/src/update.d.ts +23 -0
  74. package/build/src/{updaters/update.js → update.js} +1 -1
  75. package/build/src/updaters/changelog.d.ts +7 -10
  76. package/build/src/updaters/changelog.js +3 -9
  77. package/build/src/updaters/changelog.js.map +1 -1
  78. package/build/src/updaters/composite.d.ts +19 -0
  79. package/build/src/updaters/composite.js +42 -0
  80. package/build/src/updaters/dart/pubspec-yaml.d.ts +12 -0
  81. package/build/src/updaters/dart/pubspec-yaml.js +45 -0
  82. package/build/src/updaters/default.d.ts +21 -0
  83. package/build/src/updaters/{version-txt.js → default.js} +16 -10
  84. package/build/src/updaters/dotnet/csproj.d.ts +12 -0
  85. package/build/src/updaters/{java/google-utils.js → dotnet/csproj.js} +16 -13
  86. package/build/src/updaters/elixir/elixir-mix-exs.d.ts +12 -0
  87. package/build/src/updaters/{elixir-mix-exs.js → elixir/elixir-mix-exs.js} +12 -10
  88. package/build/src/updaters/helm/chart-yaml.d.ts +10 -11
  89. package/build/src/updaters/helm/chart-yaml.js +12 -10
  90. package/build/src/updaters/java/java-update.d.ts +14 -0
  91. package/build/src/updaters/java/{java_update.js → java-update.js} +22 -22
  92. package/build/src/updaters/java/versions-manifest.d.ts +12 -2
  93. package/build/src/updaters/java/versions-manifest.js +20 -4
  94. package/build/src/updaters/krm/krm-blueprint-version.d.ts +10 -11
  95. package/build/src/updaters/krm/krm-blueprint-version.js +13 -12
  96. package/build/src/updaters/node/package-json.d.ts +12 -0
  97. package/build/src/updaters/{package-json.js → node/package-json.js} +14 -16
  98. package/build/src/updaters/node/package-lock-json.d.ts +8 -0
  99. package/build/src/updaters/node/package-lock-json.js +36 -0
  100. package/build/src/updaters/node/samples-package-json.d.ts +23 -0
  101. package/build/src/updaters/{samples-package-json.js → node/samples-package-json.js} +19 -8
  102. package/build/src/updaters/ocaml/dune-project.d.ts +10 -11
  103. package/build/src/updaters/ocaml/dune-project.js +11 -9
  104. package/build/src/updaters/ocaml/esy-json.d.ts +10 -11
  105. package/build/src/updaters/ocaml/esy-json.js +12 -10
  106. package/build/src/updaters/ocaml/opam.d.ts +10 -11
  107. package/build/src/updaters/ocaml/opam.js +11 -9
  108. package/build/src/updaters/php/php-client-version.d.ts +12 -0
  109. package/build/src/updaters/{php-client-version.js → php/php-client-version.js} +10 -9
  110. package/build/src/updaters/php/php-manifest.d.ts +13 -0
  111. package/build/src/updaters/{php-manifest.js → php/php-manifest.js} +17 -15
  112. package/build/src/updaters/php/root-composer-update-packages.d.ts +12 -0
  113. package/build/src/updaters/{root-composer-update-packages.js → php/root-composer-update-packages.js} +17 -16
  114. package/build/src/updaters/python/pyproject-toml.d.ts +10 -11
  115. package/build/src/updaters/python/pyproject-toml.js +13 -11
  116. package/build/src/updaters/python/python-file-with-version.d.ts +7 -11
  117. package/build/src/updaters/python/python-file-with-version.js +7 -8
  118. package/build/src/updaters/python/setup-cfg.d.ts +10 -11
  119. package/build/src/updaters/python/setup-cfg.js +10 -8
  120. package/build/src/updaters/python/setup-py.d.ts +10 -11
  121. package/build/src/updaters/python/setup-py.js +10 -8
  122. package/build/src/updaters/raw-content.d.ts +19 -0
  123. package/build/src/{plugins/index.js → updaters/raw-content.js} +23 -12
  124. package/build/src/updaters/release-please-config.d.ts +8 -0
  125. package/build/src/updaters/release-please-config.js +41 -0
  126. package/build/src/updaters/release-please-manifest.d.ts +2 -11
  127. package/build/src/updaters/release-please-manifest.js +11 -14
  128. package/build/src/updaters/ruby/version-rb.d.ts +12 -0
  129. package/build/src/updaters/{version-rb.js → ruby/version-rb.js} +10 -8
  130. package/build/src/updaters/rust/cargo-lock.d.ts +7 -11
  131. package/build/src/updaters/rust/cargo-lock.js +14 -16
  132. package/build/src/updaters/rust/cargo-toml.d.ts +7 -11
  133. package/build/src/updaters/rust/cargo-toml.js +19 -21
  134. package/build/src/updaters/terraform/module-version.d.ts +10 -11
  135. package/build/src/updaters/terraform/module-version.js +11 -9
  136. package/build/src/updaters/terraform/readme.d.ts +10 -11
  137. package/build/src/updaters/terraform/readme.js +11 -10
  138. package/build/src/updaters/terraform/readme.js.map +1 -1
  139. package/build/src/util/branch-name.d.ts +5 -4
  140. package/build/src/util/branch-name.js +13 -10
  141. package/build/src/{commit-split.d.ts → util/commit-split.d.ts} +2 -4
  142. package/build/src/{commit-split.js → util/commit-split.js} +4 -2
  143. package/build/src/util/indent-commit.d.ts +1 -1
  144. package/build/src/util/logger.d.ts +5 -2
  145. package/build/src/util/logger.js +9 -4
  146. package/build/src/util/pull-request-body.d.ts +20 -0
  147. package/build/src/util/pull-request-body.js +129 -0
  148. package/build/src/util/pull-request-title.d.ts +8 -6
  149. package/build/src/util/pull-request-title.js +20 -6
  150. package/build/src/util/tag-name.d.ts +9 -0
  151. package/build/src/util/tag-name.js +41 -0
  152. package/build/src/{updaters → util}/toml-edit.d.ts +0 -0
  153. package/build/src/{updaters → util}/toml-edit.js +0 -0
  154. package/build/src/version.d.ts +11 -0
  155. package/build/src/version.js +45 -0
  156. package/build/src/versioning-strategies/always-bump-patch.d.ts +7 -0
  157. package/build/src/{updaters/package-lock-json.js → versioning-strategies/always-bump-patch.js} +8 -11
  158. package/build/src/versioning-strategies/default.d.ts +15 -0
  159. package/build/src/versioning-strategies/default.js +67 -0
  160. package/build/src/versioning-strategies/dependency-manifest.d.ts +7 -0
  161. package/build/src/versioning-strategies/dependency-manifest.js +90 -0
  162. package/build/src/versioning-strategies/java-add-snapshot.d.ts +9 -0
  163. package/build/src/versioning-strategies/java-add-snapshot.js +53 -0
  164. package/build/src/versioning-strategies/java-snapshot.d.ts +9 -0
  165. package/build/src/versioning-strategies/java-snapshot.js +67 -0
  166. package/build/src/versioning-strategies/service-pack.d.ts +7 -0
  167. package/build/src/versioning-strategies/service-pack.js +40 -0
  168. package/build/src/versioning-strategy.d.ts +28 -0
  169. package/build/src/versioning-strategy.js +55 -0
  170. package/package.json +9 -8
  171. package/build/src/constants.d.ts +0 -6
  172. package/build/src/constants.js +0 -23
  173. package/build/src/conventional-commits.d.ts +0 -53
  174. package/build/src/conventional-commits.js +0 -167
  175. package/build/src/github-release.d.ts +0 -34
  176. package/build/src/github-release.js +0 -92
  177. package/build/src/graphql-to-commits.d.ts +0 -60
  178. package/build/src/graphql-to-commits.js +0 -112
  179. package/build/src/index.d.ts +0 -94
  180. package/build/src/index.js +0 -32
  181. package/build/src/plugins/index.d.ts +0 -5
  182. package/build/src/plugins/plugin.d.ts +0 -21
  183. package/build/src/release-pr.d.ts +0 -101
  184. package/build/src/release-pr.js +0 -461
  185. package/build/src/releasers/elixir.d.ts +0 -5
  186. package/build/src/releasers/go-yoshi.d.ts +0 -10
  187. package/build/src/releasers/go-yoshi.js +0 -162
  188. package/build/src/releasers/go.d.ts +0 -6
  189. package/build/src/releasers/helm.d.ts +0 -9
  190. package/build/src/releasers/helm.js +0 -66
  191. package/build/src/releasers/index.d.ts +0 -7
  192. package/build/src/releasers/index.js +0 -72
  193. package/build/src/releasers/java/bump_type.d.ts +0 -4
  194. package/build/src/releasers/java/bump_type.js +0 -38
  195. package/build/src/releasers/java/stability.d.ts +0 -5
  196. package/build/src/releasers/java/stability.js +0 -37
  197. package/build/src/releasers/java/version.d.ts +0 -13
  198. package/build/src/releasers/java/version.js +0 -112
  199. package/build/src/releasers/java-bom.d.ts +0 -16
  200. package/build/src/releasers/java-bom.js +0 -83
  201. package/build/src/releasers/java-lts.d.ts +0 -9
  202. package/build/src/releasers/java-lts.js +0 -47
  203. package/build/src/releasers/java-yoshi.d.ts +0 -28
  204. package/build/src/releasers/java-yoshi.js +0 -304
  205. package/build/src/releasers/krm-blueprint.d.ts +0 -6
  206. package/build/src/releasers/node.d.ts +0 -10
  207. package/build/src/releasers/node.js +0 -84
  208. package/build/src/releasers/ocaml.d.ts +0 -5
  209. package/build/src/releasers/php-yoshi.d.ts +0 -5
  210. package/build/src/releasers/php-yoshi.js +0 -191
  211. package/build/src/releasers/php.d.ts +0 -7
  212. package/build/src/releasers/python.d.ts +0 -11
  213. package/build/src/releasers/python.js +0 -127
  214. package/build/src/releasers/ruby-yoshi.d.ts +0 -5
  215. package/build/src/releasers/ruby-yoshi.js +0 -142
  216. package/build/src/releasers/ruby.d.ts +0 -11
  217. package/build/src/releasers/rust.d.ts +0 -30
  218. package/build/src/releasers/rust.js +0 -163
  219. package/build/src/releasers/simple.d.ts +0 -5
  220. package/build/src/releasers/terraform-module.d.ts +0 -6
  221. package/build/src/updaters/elixir-mix-exs.d.ts +0 -13
  222. package/build/src/updaters/java/google-utils.d.ts +0 -13
  223. package/build/src/updaters/java/java_update.d.ts +0 -13
  224. package/build/src/updaters/java/pom-xml.d.ts +0 -3
  225. package/build/src/updaters/java/readme.d.ts +0 -3
  226. package/build/src/updaters/java/readme.js.map +0 -1
  227. package/build/src/updaters/package-json.d.ts +0 -16
  228. package/build/src/updaters/package-lock-json.d.ts +0 -7
  229. package/build/src/updaters/php-client-version.d.ts +0 -13
  230. package/build/src/updaters/php-manifest.d.ts +0 -13
  231. package/build/src/updaters/root-composer-update-package.d.ts +0 -13
  232. package/build/src/updaters/root-composer-update-package.js +0 -45
  233. package/build/src/updaters/root-composer-update-packages.d.ts +0 -13
  234. package/build/src/updaters/samples-package-json.d.ts +0 -13
  235. package/build/src/updaters/update.d.ts +0 -20
  236. package/build/src/updaters/version-rb.d.ts +0 -13
  237. package/build/src/updaters/version-txt.d.ts +0 -12
  238. package/build/src/updaters/version.d.ts +0 -13
  239. package/build/src/updaters/version.js +0 -31
  240. package/build/src/util/checkpoint.d.ts +0 -6
  241. package/build/src/util/checkpoint.js +0 -33
  242. package/build/src/util/release-notes.d.ts +0 -7
  243. package/build/src/util/release-notes.js +0 -34
  244. package/build/src/util/to-conventional-changelog-format.d.ts +0 -2
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
- // Copyright 2019 Google LLC
2
+ // Copyright 2021 Google LLC
3
3
  //
4
4
  // Licensed under the Apache License, Version 2.0 (the "License");
5
5
  // you may not use this file except in compliance with the License.
@@ -13,29 +13,25 @@
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.GitHub = void 0;
16
+ exports.GitHub = exports.GH_GRAPHQL_URL = exports.GH_API_URL = void 0;
17
17
  const code_suggester_1 = require("code-suggester");
18
- const logger_1 = require("./util/logger");
19
18
  const rest_1 = require("@octokit/rest");
20
19
  const request_1 = require("@octokit/request");
21
20
  const graphql_1 = require("@octokit/graphql");
22
21
  const request_error_1 = require("@octokit/request-error");
23
- function isReposListResponse(arg) {
24
- return typeof arg === 'object' && Object.hasOwnProperty.call(arg, 'name');
25
- }
26
- const chalk = require("chalk");
27
- const semver = require("semver");
28
- const graphql_to_commits_1 = require("./graphql-to-commits");
29
- const branch_name_1 = require("./util/branch-name");
30
- const constants_1 = require("./constants");
31
22
  const errors_1 = require("./errors");
32
- let probotMode = false;
23
+ const MAX_ISSUE_BODY_SIZE = 65536;
24
+ exports.GH_API_URL = 'https://api.github.com';
25
+ exports.GH_GRAPHQL_URL = 'https://api.github.com';
26
+ const logger_1 = require("./util/logger");
27
+ const manifest_1 = require("./manifest");
28
+ const signoff_commit_message_1 = require("./util/signoff-commit-message");
33
29
  class GitHub {
34
30
  constructor(options) {
35
31
  this.graphqlRequest = wrapAsync(async (opts, maxRetries = 1) => {
36
32
  while (maxRetries >= 0) {
37
33
  try {
38
- return await this.makeGraphqlRequest(opts);
34
+ return await this.graphql(opts);
39
35
  }
40
36
  catch (err) {
41
37
  if (err.status !== 502) {
@@ -46,308 +42,30 @@ class GitHub {
46
42
  }
47
43
  });
48
44
  /**
49
- * Returns the list of commits since a given SHA on the target branch
50
- *
51
- * @param {string} sha SHA of the base commit or undefined for all commits
52
- * @param {string} path If provided, limit to commits that affect the provided path
53
- * @param {number} per_page Pagination option. Defaults to 100
54
- * @returns {Commit[]} List of commits
55
- * @throws {GitHubAPIError} on an API error
56
- */
57
- this.commitsSinceShaRest = wrapAsync(async (sha, path, per_page = 100) => {
58
- let page = 1;
59
- let found = false;
60
- const baseBranch = await this.getDefaultBranch();
61
- const commits = [];
62
- while (!found) {
63
- const response = await this.request('GET /repos/{owner}/{repo}/commits{?sha,page,per_page,path}', {
64
- owner: this.owner,
65
- repo: this.repo,
66
- sha: baseBranch,
67
- page,
68
- per_page,
69
- path,
70
- });
71
- for (const commit of response.data) {
72
- if (commit.sha === sha) {
73
- found = true;
74
- break;
75
- }
76
- // skip merge commits
77
- if (commit.parents.length === 2) {
78
- continue;
79
- }
80
- commits.push([commit.sha, commit.commit.message]);
81
- }
82
- page++;
83
- }
84
- const ret = [];
85
- for (const [ref, message] of commits) {
86
- const files = [];
87
- let page = 1;
88
- let moreFiles = true;
89
- while (moreFiles) {
90
- // the "Get Commit" resource is a bit of an outlier in terms of GitHub's
91
- // normal pagination: https://git.io/JmVZq
92
- // The behavior is to return an object representing the commit, a
93
- // property of which is an array of files. GitHub will return as many
94
- // associated files as there are, up to a limit of 300, on the initial
95
- // request. If there are more associated files, it will send "Links"
96
- // headers to get the next set. There is a total limit of 3000
97
- // files returned per commit.
98
- // In practice, the links headers are just the same resourceID plus a
99
- // "page=N" query parameter with "page=1" being the initial set.
100
- //
101
- // TODO: it is more robust to follow the link.next headers (in case
102
- // GitHub ever changes the pattern) OR use ocktokit pagination for this
103
- // endpoint when https://git.io/JmVll is addressed.
104
- const response = (await this.request('GET /repos/{owner}/{repo}/commits/{ref}{?page}', { owner: this.owner, repo: this.repo, ref, page }));
105
- const commitFiles = response.data.files;
106
- if (!commitFiles) {
107
- moreFiles = false;
108
- break;
109
- }
110
- files.push(...commitFiles.map(f => { var _a; return (_a = f.filename) !== null && _a !== void 0 ? _a : ''; }));
111
- // < 300 files means we hit the end
112
- // page === 10 means we're at 3000 and that's the limit GH is gonna
113
- // cough up anyway.
114
- if (commitFiles.length < 300 || page === 10) {
115
- moreFiles = false;
116
- break;
117
- }
118
- page++;
119
- }
120
- ret.push({ sha: ref, message, files });
121
- }
122
- return ret;
123
- });
124
- /**
125
- * Find the SHA of the commit at the provided tag.
126
- *
127
- * @param {string} name Tag name
128
- * @returns {string} The SHA of the commit
129
- * @throws {GitHubAPIError} on an API error
130
- */
131
- this.getTagSha = wrapAsync(async (name) => {
132
- const refResponse = (await this.request('GET /repos/:owner/:repo/git/refs/tags/:name', {
133
- owner: this.owner,
134
- repo: this.repo,
135
- name,
136
- }));
137
- return refResponse.data.object.sha;
138
- });
139
- this.allTags = wrapAsync(async (prefix) => {
140
- // If we've fallen back to using allTags, support "-", "@", and "/" as a
141
- // suffix separating the library name from the version #. This allows
142
- // a repository to be seamlessly be migrated from a tool like lerna:
143
- const prefixes = [];
144
- if (prefix) {
145
- prefix = prefix.substring(0, prefix.length - 1);
146
- for (const suffix of ['-', '@', '/']) {
147
- prefixes.push(`${prefix}${suffix}`);
148
- }
149
- }
150
- const tags = {};
151
- for await (const response of this.octokit.paginate.iterator(this.decoratePaginateOpts({
152
- method: 'GET',
153
- url: `/repos/${this.owner}/${this.repo}/tags?per_page=100`,
154
- }))) {
155
- response.data.forEach(data => {
156
- // For monorepos, a prefix can be provided, indicating that only tags
157
- // matching the prefix should be returned:
158
- if (!isReposListResponse(data))
159
- return;
160
- let version = data.name;
161
- if (prefix) {
162
- let match = false;
163
- for (prefix of prefixes) {
164
- if (data.name.startsWith(prefix)) {
165
- version = data.name.replace(prefix, '');
166
- match = true;
167
- }
168
- }
169
- if (!match)
170
- return;
171
- }
172
- if (semver.valid(version)) {
173
- version = semver.valid(version);
174
- tags[version] = { sha: data.commit.sha, name: data.name, version };
175
- }
176
- });
177
- }
178
- return tags;
179
- });
180
- /**
181
- * Return a list of merged pull requests. The list is not guaranteed to be sorted
182
- * by merged_at, but is generally most recent first.
45
+ * Return a list of tags. The list is not guaranteed to be sorted.
183
46
  *
184
- * @param {string} targetBranch - Base branch of the pull request. Defaults to
185
- * the configured default branch.
186
47
  * @param {number} page - Page of results. Defaults to 1.
187
48
  * @param {number} perPage - Number of results per page. Defaults to 100.
188
- * @returns {MergedGitHubPR[]} - List of merged pull requests
49
+ * @returns {GitHubRelease[]} - List of tags
189
50
  * @throws {GitHubAPIError} on an API error
190
51
  */
191
- this.findMergedPullRequests = wrapAsync(async (targetBranch, page = 1, perPage = 100) => {
192
- if (!targetBranch) {
193
- targetBranch = await this.getDefaultBranch();
194
- }
195
- // TODO: is sorting by updated better?
196
- const pullsResponse = (await this.request(`GET /repos/:owner/:repo/pulls?state=closed&per_page=${perPage}&page=${page}&base=${targetBranch}&sort=created&direction=desc`, {
197
- owner: this.owner,
198
- repo: this.repo,
199
- }));
200
- // TODO: distinguish between no more pages and a full page of
201
- // closed, non-merged pull requests. At page size of 100, this unlikely
202
- // to matter
203
- if (!pullsResponse.data) {
204
- return [];
205
- }
206
- return (pullsResponse.data
207
- // only return merged pull requests
208
- .filter(pull => {
209
- return !!pull.merged_at;
210
- })
211
- .map(pull => {
212
- const labels = pull.labels
213
- ? pull.labels.map(l => {
214
- return l.name + '';
215
- })
216
- : [];
52
+ this.listReleases = wrapAsync(async (page = 1, perPage = 100) => {
53
+ logger_1.logger.debug(`Fetching releases page ${page}`);
54
+ const releases = await this.octokit.repos.listReleases({
55
+ owner: this.repository.owner,
56
+ repo: this.repository.repo,
57
+ page,
58
+ per_page: perPage,
59
+ });
60
+ return releases.data.map(release => {
217
61
  return {
218
- sha: pull.merge_commit_sha,
219
- number: pull.number,
220
- baseRefName: pull.base.ref,
221
- headRefName: pull.head.ref,
222
- labels,
223
- title: pull.title,
224
- body: pull.body + '',
62
+ name: release.name || undefined,
63
+ tagName: release.tag_name,
64
+ sha: release.target_commitish,
65
+ notes: release.body_text,
66
+ url: release.html_url,
225
67
  };
226
- }));
227
- });
228
- /**
229
- * Find an existing release pull request with a matching title and labels
230
- *
231
- * @param {string} title Substring to match against the issue title
232
- * @param {string[]} labels List of labels to match the issues
233
- * @return {IssuesListResponseItem|undefined}
234
- * @throws {AuthError} if the user is not authenticated to make this request
235
- * @throws {GitHubAPIError} on other API errors
236
- */
237
- this.findExistingReleaseIssue = wrapAsync(async (title, labels) => {
238
- for await (const response of this.octokit.paginate.iterator(this.decoratePaginateOpts({
239
- method: 'GET',
240
- url: `/repos/${this.owner}/${this.repo}/issues?labels=${labels.join(',')}`,
241
- per_page: 100,
242
- }))) {
243
- for (let i = 0; response.data[i] !== undefined; i++) {
244
- const issue = response.data[i];
245
- if (issue.title.indexOf(title) !== -1 && issue.state === 'open') {
246
- return issue;
247
- }
248
- }
249
- }
250
- return undefined;
251
- }, e => {
252
- if (e instanceof request_error_1.RequestError && e.status === 404) {
253
- // the most likely cause of a 404 during this step is actually
254
- // that the user does not have access to the repo:
255
- throw new errors_1.AuthError(e);
256
- }
257
- });
258
- /**
259
- * Open a pull request
260
- *
261
- * @param {GitHubPR} options The pull request options
262
- * @throws {GitHubAPIError} on an API error
263
- */
264
- this.openPR = wrapAsync(async (options) => {
265
- var _a;
266
- const defaultBranch = await this.getDefaultBranch();
267
- // check if there's an existing PR, so that we can opt to update it
268
- // rather than creating a new PR.
269
- const refName = `refs/heads/${options.branch}`;
270
- let openReleasePR;
271
- const releasePRCandidates = await this.findOpenReleasePRs(options.labels);
272
- for (const releasePR of releasePRCandidates) {
273
- if (refName && refName.includes(releasePR.head.ref)) {
274
- openReleasePR = releasePR;
275
- break;
276
- }
277
- }
278
- // Short-circuit if there have been no changes to the pull-request body.
279
- if (openReleasePR && openReleasePR.body === options.body) {
280
- logger_1.logger.info(`PR https://github.com/${this.owner}/${this.repo}/pull/${openReleasePR.number} remained the same`);
281
- return undefined;
282
- }
283
- // Update the files for the release if not already supplied
284
- const changes = (_a = options.changes) !== null && _a !== void 0 ? _a : (await this.getChangeSet(options.updates, defaultBranch));
285
- const prNumber = await code_suggester_1.createPullRequest(this.octokit, changes, {
286
- upstreamOwner: this.owner,
287
- upstreamRepo: this.repo,
288
- title: options.title,
289
- branch: options.branch,
290
- description: options.body.slice(0, constants_1.MAX_ISSUE_BODY_SIZE),
291
- primary: defaultBranch,
292
- force: true,
293
- fork: this.fork,
294
- message: options.message,
295
- logger: logger_1.logger,
296
- });
297
- // If a release PR was already open, update the title and body:
298
- if (openReleasePR) {
299
- logger_1.logger.info(`update pull-request #${openReleasePR.number}: ${chalk.yellow(options.title)}`);
300
- await this.request('PATCH /repos/:owner/:repo/pulls/:pull_number', {
301
- pull_number: openReleasePR.number,
302
- owner: this.owner,
303
- repo: this.repo,
304
- title: options.title,
305
- body: options.body,
306
- state: 'open',
307
- });
308
- return openReleasePR.number;
309
- }
310
- else {
311
- return prNumber;
312
- }
313
- });
314
- /**
315
- * Returns the repository's default/primary branch.
316
- *
317
- * @returns {string}
318
- * @throws {GitHubAPIError} on an API error
319
- */
320
- this.getRepositoryDefaultBranch = wrapAsync(async () => {
321
- if (this.repositoryDefaultBranch) {
322
- return this.repositoryDefaultBranch;
323
- }
324
- const { data } = await this.octokit.repos.get({
325
- repo: this.repo,
326
- owner: this.owner,
327
- headers: {
328
- Authorization: `token ${this.token}`,
329
- },
330
68
  });
331
- this.repositoryDefaultBranch = data.default_branch;
332
- return this.repositoryDefaultBranch;
333
- });
334
- /**
335
- * Close a pull request
336
- *
337
- * @param {number} prNumber The pull request number
338
- * @returns {boolean} Whether the request was attempts
339
- * @throws {GitHubAPIError} on an API error
340
- */
341
- this.closePR = wrapAsync(async (prNumber) => {
342
- if (this.fork)
343
- return false;
344
- await this.request('PATCH /repos/:owner/:repo/pulls/:pull_number', {
345
- owner: this.owner,
346
- repo: this.repo,
347
- pull_number: prNumber,
348
- state: 'closed',
349
- });
350
- return true;
351
69
  });
352
70
  /**
353
71
  * Fetch the contents of a file with the Contents API
@@ -358,10 +76,10 @@ class GitHub {
358
76
  * @throws {GitHubAPIError} on other API errors
359
77
  */
360
78
  this.getFileContentsWithSimpleAPI = wrapAsync(async (path, ref, isBranch = true) => {
361
- ref = isBranch ? GitHub.fullyQualifyBranchRef(ref) : ref;
79
+ ref = isBranch ? fullyQualifyBranchRef(ref) : ref;
362
80
  const options = {
363
- owner: this.owner,
364
- repo: this.repo,
81
+ owner: this.repository.owner,
82
+ repo: this.repository.repo,
365
83
  path,
366
84
  ref,
367
85
  };
@@ -373,79 +91,33 @@ class GitHub {
373
91
  };
374
92
  });
375
93
  /**
376
- * Fetch the contents of a file
94
+ * Fetch the contents of a file using the Git data API
377
95
  *
378
96
  * @param {string} path The path to the file in the repository
379
97
  * @param {string} branch The branch to fetch from
380
98
  * @returns {GitHubFileContents}
381
99
  * @throws {GitHubAPIError} on other API errors
382
100
  */
383
- this.getFileContentsOnBranch = wrapAsync(async (path, branch) => {
384
- try {
385
- return await this.getFileContentsWithSimpleAPI(path, branch);
386
- }
387
- catch (err) {
388
- if (err.status === 403) {
389
- return await this.getFileContentsWithDataAPI(path, branch);
390
- }
391
- throw err;
392
- }
393
- });
394
- /**
395
- * Create a GitHub release
396
- *
397
- * @param {string} packageName name of the package
398
- * @param {string} tagName tag to create
399
- * @param {string} sha SHA of commit to tag at
400
- * @param {string} releaseNotes Notes to add to release
401
- * @param {boolean} draft Whether or not to create the release as a draft
402
- * @throws {DuplicateReleaseError} if the release tag already exists
403
- * @throws {GitHubAPIError} on other API errors
404
- */
405
- this.createRelease = wrapAsync(async (packageName, tagName, sha, releaseNotes, draft) => {
406
- logger_1.logger.info(`creating release ${tagName}`);
407
- const name = packageName ? `${packageName} ${tagName}` : tagName;
408
- return (await this.request('POST /repos/:owner/:repo/releases', {
409
- owner: this.owner,
410
- repo: this.repo,
411
- tag_name: tagName,
412
- target_commitish: sha,
413
- body: releaseNotes,
414
- name,
415
- draft: draft,
416
- })).data;
417
- }, e => {
418
- if (e instanceof request_error_1.RequestError) {
419
- if (e.status === 422 &&
420
- errors_1.GitHubAPIError.parseErrors(e).some(error => {
421
- return error.code === 'already_exists';
422
- })) {
423
- throw new errors_1.DuplicateReleaseError(e, 'tagName');
424
- }
425
- }
426
- });
427
- /**
428
- * Remove labels from an issue or pull request
429
- *
430
- * @param {string[]} labels The names of the labels to remove
431
- * @param {number} prNumber The issue or pull request number
432
- * @return {boolean} Whether or not the request was attempted
433
- * @throws {GitHubAPIError} on an API error
434
- */
435
- this.removeLabels = wrapAsync(async (labels, prNumber) => {
436
- if (this.fork)
437
- return false;
438
- for (let i = 0, label; i < labels.length; i++) {
439
- label = labels[i];
440
- logger_1.logger.info(`removing label ${chalk.green(label)} from ${chalk.green('' + prNumber)}`);
441
- await this.request('DELETE /repos/:owner/:repo/issues/:issue_number/labels/:name', {
442
- owner: this.owner,
443
- repo: this.repo,
444
- issue_number: prNumber,
445
- name: label,
446
- });
447
- }
448
- return true;
101
+ this.getFileContentsWithDataAPI = wrapAsync(async (path, branch) => {
102
+ const repoTree = await this.octokit.git.getTree({
103
+ owner: this.repository.owner,
104
+ repo: this.repository.repo,
105
+ tree_sha: branch,
106
+ });
107
+ const blobDescriptor = repoTree.data.tree.find(tree => tree.path === path);
108
+ if (!blobDescriptor) {
109
+ throw new Error(`Could not find requested path: ${path}`);
110
+ }
111
+ const resp = await this.octokit.git.getBlob({
112
+ owner: this.repository.owner,
113
+ repo: this.repository.repo,
114
+ file_sha: blobDescriptor.sha,
115
+ });
116
+ return {
117
+ parsedContent: Buffer.from(resp.data.content, 'base64').toString('utf8'),
118
+ content: resp.data.content,
119
+ sha: resp.data.sha,
120
+ };
449
121
  });
450
122
  /**
451
123
  * Returns a list of paths to all files with a given name.
@@ -460,11 +132,12 @@ class GitHub {
460
132
  */
461
133
  this.findFilesByFilenameAndRef = wrapAsync(async (filename, ref, prefix) => {
462
134
  if (prefix) {
463
- prefix = this.normalizePrefix(prefix);
135
+ prefix = normalizePrefix(prefix);
464
136
  }
137
+ logger_1.logger.debug(`finding files by filename: ${filename}, ref: ${ref}, prefix: ${prefix}`);
465
138
  const response = await this.octokit.git.getTree({
466
- owner: this.owner,
467
- repo: this.repo,
139
+ owner: this.repository.owner,
140
+ repo: this.repository.repo,
468
141
  tree_sha: ref,
469
142
  recursive: 'true',
470
143
  });
@@ -487,6 +160,101 @@ class GitHub {
487
160
  return path;
488
161
  });
489
162
  });
163
+ this.createPullRequest = wrapAsync(async (pullRequest, targetBranch, message, updates, options) => {
164
+ // Update the files for the release if not already supplied
165
+ const changes = await this.getChangeSet(updates, targetBranch);
166
+ const prNumber = await code_suggester_1.createPullRequest(this.octokit, changes, {
167
+ upstreamOwner: this.repository.owner,
168
+ upstreamRepo: this.repository.repo,
169
+ title: pullRequest.title,
170
+ branch: pullRequest.headBranchName,
171
+ description: pullRequest.body,
172
+ primary: targetBranch,
173
+ force: true,
174
+ fork: !!(options === null || options === void 0 ? void 0 : options.fork),
175
+ message,
176
+ logger: logger_1.logger,
177
+ draft: !!(options === null || options === void 0 ? void 0 : options.draft),
178
+ labels: pullRequest.labels,
179
+ });
180
+ return await this.getPullRequest(prNumber);
181
+ });
182
+ /**
183
+ * Fetch a pull request given the pull number
184
+ * @param {number} number The pull request number
185
+ * @returns {PullRequest}
186
+ */
187
+ this.getPullRequest = wrapAsync(async (number) => {
188
+ const response = await this.octokit.pulls.get({
189
+ owner: this.repository.owner,
190
+ repo: this.repository.repo,
191
+ pull_number: number,
192
+ });
193
+ return {
194
+ headBranchName: response.data.head.ref,
195
+ baseBranchName: response.data.base.ref,
196
+ number: response.data.number,
197
+ title: response.data.title,
198
+ body: response.data.body || '',
199
+ files: [],
200
+ labels: response.data.labels
201
+ .map(label => label.name)
202
+ .filter(name => !!name),
203
+ };
204
+ });
205
+ /**
206
+ * Update a pull request's title and body.
207
+ * @param {number} number The pull request number
208
+ * @param {ReleasePullRequest} releasePullRequest Pull request data to update
209
+ * @param {}
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.getChangeSet(releasePullRequest.updates, targetBranch);
214
+ let message = releasePullRequest.title.toString();
215
+ if (options === null || options === void 0 ? void 0 : options.signoffUser) {
216
+ message = signoff_commit_message_1.signoffCommitMessage(message, options.signoffUser);
217
+ }
218
+ const title = releasePullRequest.title.toString();
219
+ const body = releasePullRequest.body
220
+ .toString()
221
+ .slice(0, MAX_ISSUE_BODY_SIZE);
222
+ const prNumber = await code_suggester_1.createPullRequest(this.octokit, changes, {
223
+ upstreamOwner: this.repository.owner,
224
+ upstreamRepo: this.repository.repo,
225
+ title,
226
+ branch: releasePullRequest.headRefName,
227
+ description: body,
228
+ primary: targetBranch,
229
+ force: true,
230
+ fork: (options === null || options === void 0 ? void 0 : options.fork) === false ? false : true,
231
+ message,
232
+ logger: logger_1.logger,
233
+ draft: releasePullRequest.draft,
234
+ });
235
+ if (prNumber !== number) {
236
+ logger_1.logger.warn(`updated code for ${prNumber}, but update requested for ${number}`);
237
+ }
238
+ const response = await this.octokit.pulls.update({
239
+ owner: this.repository.owner,
240
+ repo: this.repository.repo,
241
+ pull_number: number,
242
+ title: releasePullRequest.title.toString(),
243
+ body,
244
+ state: 'open',
245
+ });
246
+ return {
247
+ headBranchName: response.data.head.ref,
248
+ baseBranchName: response.data.base.ref,
249
+ number: response.data.number,
250
+ title: response.data.title,
251
+ body: response.data.body || '',
252
+ files: [],
253
+ labels: response.data.labels
254
+ .map(label => label.name)
255
+ .filter(name => !!name),
256
+ };
257
+ });
490
258
  /**
491
259
  * Returns a list of paths to all files with a given file
492
260
  * extension.
@@ -503,11 +271,11 @@ class GitHub {
503
271
  */
504
272
  this.findFilesByExtensionAndRef = wrapAsync(async (extension, ref, prefix) => {
505
273
  if (prefix) {
506
- prefix = this.normalizePrefix(prefix);
274
+ prefix = normalizePrefix(prefix);
507
275
  }
508
276
  const response = await this.octokit.git.getTree({
509
- owner: this.owner,
510
- repo: this.repo,
277
+ owner: this.repository.owner,
278
+ repo: this.repository.repo,
511
279
  tree_sha: ref,
512
280
  recursive: 'true',
513
281
  });
@@ -530,6 +298,39 @@ class GitHub {
530
298
  return path;
531
299
  });
532
300
  });
301
+ /**
302
+ * Create a GitHub release
303
+ *
304
+ * @param {Release} release Release parameters
305
+ * @param {boolean} draft Whether or not to create the release as a draft
306
+ * @throws {DuplicateReleaseError} if the release tag already exists
307
+ * @throws {GitHubAPIError} on other API errors
308
+ */
309
+ this.createRelease = wrapAsync(async (release) => {
310
+ const resp = await this.octokit.repos.createRelease({
311
+ owner: this.repository.owner,
312
+ repo: this.repository.repo,
313
+ tag_name: release.tag.toString(),
314
+ body: release.notes,
315
+ sha: release.sha,
316
+ });
317
+ return {
318
+ name: resp.data.name || undefined,
319
+ tagName: resp.data.tag_name,
320
+ sha: resp.data.target_commitish,
321
+ notes: resp.data.body_text,
322
+ url: resp.data.html_url,
323
+ };
324
+ }, e => {
325
+ if (e instanceof request_error_1.RequestError) {
326
+ if (e.status === 422 &&
327
+ errors_1.GitHubAPIError.parseErrors(e).some(error => {
328
+ return error.code === 'already_exists';
329
+ })) {
330
+ throw new errors_1.DuplicateReleaseError(e, 'tagName');
331
+ }
332
+ }
333
+ });
533
334
  /**
534
335
  * Makes a comment on a issue/pull request.
535
336
  *
@@ -538,379 +339,176 @@ class GitHub {
538
339
  * @throws {GitHubAPIError} on an API error
539
340
  */
540
341
  this.commentOnIssue = wrapAsync(async (comment, number) => {
541
- logger_1.logger.info(`adding comment to https://github.com/${this.owner}/${this.repo}/issue/${number}`);
542
- return (await this.request('POST /repos/:owner/:repo/issues/:issue_number/comments', {
543
- owner: this.owner,
544
- repo: this.repo,
342
+ logger_1.logger.debug(`adding comment to https://github.com/${this.repository.owner}/${this.repository.repo}/issue/${number}`);
343
+ const resp = await this.octokit.issues.createComment({
344
+ owner: this.repository.owner,
345
+ repo: this.repository.repo,
545
346
  issue_number: number,
546
347
  body: comment,
547
- })).data;
348
+ });
349
+ return resp.data.html_url;
350
+ });
351
+ /**
352
+ * Removes labels from an issue/pull request.
353
+ *
354
+ * @param {string[]} labels The labels to remove.
355
+ * @param {number} number The issue/pull request number.
356
+ */
357
+ this.removeIssueLabels = wrapAsync(async (labels, number) => {
358
+ if (labels.length === 0) {
359
+ return;
360
+ }
361
+ logger_1.logger.debug(`removing labels: ${labels} from issue/pull ${number}`);
362
+ await Promise.all(labels.map(label => this.octokit.issues.removeLabel({
363
+ owner: this.repository.owner,
364
+ repo: this.repository.repo,
365
+ issue_number: number,
366
+ name: label,
367
+ })));
548
368
  });
549
- this.defaultBranch = options.defaultBranch;
550
- this.token = options.token;
551
- this.owner = options.owner;
552
- this.repo = options.repo;
553
- this.fork = !!options.fork;
554
- this.apiUrl = options.apiUrl || constants_1.GH_API_URL;
555
- if (options.octokitAPIs === undefined) {
556
- this.octokit = new rest_1.Octokit({
557
- baseUrl: options.apiUrl,
558
- auth: this.token,
369
+ /**
370
+ * Adds label to an issue/pull request.
371
+ *
372
+ * @param {string[]} labels The labels to add.
373
+ * @param {number} number The issue/pull request number.
374
+ */
375
+ this.addIssueLabels = wrapAsync(async (labels, number) => {
376
+ if (labels.length === 0) {
377
+ return;
378
+ }
379
+ logger_1.logger.debug(`adding labels: ${labels} from issue/pull ${number}`);
380
+ await this.octokit.issues.addLabels({
381
+ owner: this.repository.owner,
382
+ repo: this.repository.repo,
383
+ issue_number: number,
384
+ labels,
559
385
  });
560
- const defaults = {
561
- baseUrl: this.apiUrl,
562
- headers: {
563
- 'user-agent': `${constants_1.RELEASE_PLEASE}/${require('../../package.json').version}`,
564
- Authorization: `token ${this.token}`,
565
- },
566
- };
567
- this.request = request_1.request.defaults(defaults);
568
- this.graphql = graphql_1.graphql;
569
- }
570
- else {
571
- // for the benefit of probot applications, we allow a configured instance
572
- // of octokit to be passed in as a parameter.
573
- probotMode = true;
574
- this.octokit = options.octokitAPIs.octokit;
575
- this.request = options.octokitAPIs.request;
576
- this.graphql = options.octokitAPIs.graphql;
577
- }
386
+ });
387
+ this.repository = options.repository;
388
+ this.octokit = options.octokitAPIs.octokit;
389
+ this.request = options.octokitAPIs.request;
390
+ this.graphql = options.octokitAPIs.graphql;
578
391
  }
579
- async makeGraphqlRequest(_opts) {
580
- let opts = Object.assign({}, _opts);
581
- if (!probotMode) {
582
- opts = Object.assign(opts, {
583
- url: `${this.apiUrl}/graphql`,
392
+ /**
393
+ * Build a new GitHub client with auto-detected default branch.
394
+ *
395
+ * @param {GitHubCreateOptions} options Configuration options
396
+ * @param {string} options.owner The repository owner.
397
+ * @param {string} options.repo The repository name.
398
+ * @param {string} options.defaultBranch Optional. The repository's default branch.
399
+ * Defaults to the value fetched via the API.
400
+ * @param {string} options.apiUrl Optional. The base url of the GitHub API.
401
+ * @param {string} options.graphqlUrl Optional. The base url of the GraphQL API.
402
+ * @param {OctokitAPISs} options.octokitAPIs Optional. Override the internal
403
+ * client instances with a pre-authenticated instance.
404
+ * @param {string} token Optional. A GitHub API token used for authentication.
405
+ */
406
+ static async create(options) {
407
+ var _a, _b, _c, _d;
408
+ const apiUrl = (_a = options.apiUrl) !== null && _a !== void 0 ? _a : exports.GH_API_URL;
409
+ const graphqlUrl = (_b = options.graphqlUrl) !== null && _b !== void 0 ? _b : exports.GH_GRAPHQL_URL;
410
+ const releasePleaseVersion = require('../../package.json').version;
411
+ const apis = (_c = options.octokitAPIs) !== null && _c !== void 0 ? _c : {
412
+ octokit: new rest_1.Octokit({
413
+ baseUrl: apiUrl,
414
+ auth: options.token,
415
+ }),
416
+ request: request_1.request.defaults({
417
+ baseUrl: apiUrl,
584
418
  headers: {
585
- authorization: `token ${this.token}`,
586
- 'content-type': 'application/vnd.github.v3+json',
419
+ 'user-agent': `release-please/${releasePleaseVersion}`,
420
+ Authorization: `token ${options.token}`,
587
421
  },
588
- });
589
- }
590
- return this.graphql(opts);
591
- }
592
- decoratePaginateOpts(opts) {
593
- if (probotMode) {
594
- return opts;
595
- }
596
- else {
597
- return Object.assign(opts, {
422
+ }),
423
+ graphql: graphql_1.graphql.defaults({
424
+ baseUrl: graphqlUrl,
598
425
  headers: {
599
- Authorization: `token ${this.token}`,
426
+ 'user-agent': `release-please/${releasePleaseVersion}`,
427
+ Authorization: `token ${options.token}`,
428
+ 'content-type': 'application/vnd.github.v3+json',
600
429
  },
601
- });
602
- }
603
- }
604
- /**
605
- * Returns the list of commits since a given SHA on the target branch
606
- *
607
- * Note: Commit.files only for commits from PRs.
608
- *
609
- * @param {string|undefined} sha SHA of the base commit or undefined for all commits
610
- * @param {number} perPage Pagination option. Defaults to 100
611
- * @param {boolean} labels Whether or not to return labels. Defaults to false
612
- * @param {string|null} path If provided, limit to commits that affect the provided path
613
- * @returns {Commit[]} List of commits
614
- * @throws {GitHubAPIError} on an API error
615
- */
616
- async commitsSinceSha(sha, perPage = 100, labels = false, path = null) {
617
- const commits = [];
618
- const method = labels ? 'commitsWithLabels' : 'commitsWithFiles';
619
- let cursor;
620
- for (;;) {
621
- const commitsResponse = await this[method](cursor, perPage, path);
622
- for (let i = 0, commit; i < commitsResponse.commits.length; i++) {
623
- commit = commitsResponse.commits[i];
624
- if (commit.sha === sha) {
625
- return commits;
626
- }
627
- else {
628
- commits.push(commit);
629
- }
630
- }
631
- if (commitsResponse.hasNextPage === false || !commitsResponse.endCursor) {
632
- return commits;
633
- }
634
- else {
635
- cursor = commitsResponse.endCursor;
636
- }
637
- }
638
- }
639
- async commitsWithFiles(cursor = undefined, perPage = 32, path = null, maxFilesChanged = 64) {
640
- const baseBranch = await this.getDefaultBranch();
641
- // The GitHub v3 API does not offer an elegant way to fetch commits
642
- // in conjucntion with the path that they modify. We lean on the graphql
643
- // API for this one task, fetching commits in descending chronological
644
- // order along with the file paths attached to them.
645
- const response = await this.graphqlRequest({
646
- query: `query commitsWithFiles($cursor: String, $owner: String!, $repo: String!, $baseRef: String!, $perPage: Int, $maxFilesChanged: Int, $path: String) {
647
- repository(owner: $owner, name: $repo) {
648
- ref(qualifiedName: $baseRef) {
649
- target {
650
- ... on Commit {
651
- history(first: $perPage, after: $cursor, path: $path) {
652
- edges {
653
- node {
654
- ... on Commit {
655
- message
656
- oid
657
- associatedPullRequests(first: 1) {
658
- edges {
659
- node {
660
- ... on PullRequest {
661
- number
662
- mergeCommit {
663
- oid
664
- }
665
- files(first: $maxFilesChanged) {
666
- edges {
667
- node {
668
- path
669
- }
670
- }
671
- pageInfo {
672
- endCursor
673
- hasNextPage
674
- }
675
- }
676
- }
677
- }
678
- }
679
- }
680
- }
681
- }
682
- }
683
- pageInfo {
684
- endCursor
685
- hasNextPage
686
- }
687
- }
688
- }
689
- }
690
- }
691
- }
692
- }`,
693
- cursor,
694
- maxFilesChanged,
695
- owner: this.owner,
696
- path,
697
- perPage,
698
- repo: this.repo,
699
- baseRef: `refs/heads/${baseBranch}`,
700
- }, 3);
701
- return graphql_to_commits_1.graphqlToCommits(this, response);
702
- }
703
- async commitsWithLabels(cursor = undefined, perPage = 32, path = null, maxLabels = 16) {
704
- const baseBranch = await this.getDefaultBranch();
705
- const response = await this.graphqlRequest({
706
- query: `query commitsWithLabels($cursor: String, $owner: String!, $repo: String!, $baseRef: String!, $perPage: Int, $maxLabels: Int, $path: String) {
707
- repository(owner: $owner, name: $repo) {
708
- ref(qualifiedName: $baseRef) {
709
- target {
710
- ... on Commit {
711
- history(first: $perPage, after: $cursor, path: $path) {
712
- edges {
713
- node {
714
- ... on Commit {
715
- message
716
- oid
717
- associatedPullRequests(first: 1) {
718
- edges {
719
- node {
720
- ... on PullRequest {
721
- number
722
- mergeCommit {
723
- oid
724
- }
725
- labels(first: $maxLabels) {
726
- edges {
727
- node {
728
- name
729
- }
730
- }
731
- }
732
- }
733
- }
734
- }
735
- }
736
- }
737
- }
738
- }
739
- pageInfo {
740
- endCursor
741
- hasNextPage
742
- }
743
- }
744
- }
745
- }
746
- }
747
- }
748
- }`,
749
- cursor,
750
- maxLabels,
751
- owner: this.owner,
752
- path,
753
- perPage,
754
- repo: this.repo,
755
- baseRef: `refs/heads/${baseBranch}`,
756
- }, 3);
757
- return graphql_to_commits_1.graphqlToCommits(this, response);
430
+ }),
431
+ };
432
+ const opts = {
433
+ repository: {
434
+ owner: options.owner,
435
+ repo: options.repo,
436
+ defaultBranch: (_d = options.defaultBranch) !== null && _d !== void 0 ? _d : (await GitHub.defaultBranch(options.owner, options.repo, apis.octokit)),
437
+ },
438
+ octokitAPIs: apis,
439
+ };
440
+ return new GitHub(opts);
758
441
  }
759
442
  /**
760
- * Return the pull request files
443
+ * Returns the default branch for a given repository.
761
444
  *
762
- * @param {number} num Pull request number
763
- * @param {string} cursor Pagination cursor
764
- * @param {number} maxFilesChanged Number of files to return per page
765
- * @return {PREdge}
766
- * @throws {GitHubAPIError} on an API error
445
+ * @param {string} owner The GitHub repository owner
446
+ * @param {string} repo The GitHub repository name
447
+ * @param {OctokitType} octokit An authenticated octokit instance
448
+ * @returns {string} Name of the default branch
767
449
  */
768
- async pullRequestFiles(num, cursor, maxFilesChanged = 100) {
769
- // Used to handle the edge-case in which a PR has more than 100
770
- // modified files attached to it.
771
- const response = await this.graphqlRequest({
772
- query: `query pullRequestFiles($cursor: String, $owner: String!, $repo: String!, $maxFilesChanged: Int, $num: Int!) {
773
- repository(owner: $owner, name: $repo) {
774
- pullRequest(number: $num) {
775
- number
776
- files(first: $maxFilesChanged, after: $cursor) {
777
- edges {
778
- node {
779
- path
780
- }
781
- }
782
- pageInfo {
783
- endCursor
784
- hasNextPage
785
- }
786
- }
787
- }
788
- }
789
- }`,
790
- cursor,
791
- maxFilesChanged,
792
- owner: this.owner,
793
- repo: this.repo,
794
- num,
450
+ static async defaultBranch(owner, repo, octokit) {
451
+ const { data } = await octokit.repos.get({
452
+ repo,
453
+ owner,
795
454
  });
796
- return { node: response.repository.pullRequest };
455
+ return data.default_branch;
797
456
  }
798
457
  /**
799
- * Find the "last" merged PR given a headBranch. "last" here means
800
- * the most recently created. Includes all associated files.
458
+ * Returns the list of commits to the default branch after the provided filter
459
+ * query has been satified.
801
460
  *
802
- * @param {string} headBranch - e.g. "release-please/branches/main"
803
- * @returns {MergedGitHubPRWithFiles} - if found, otherwise undefined.
461
+ * @param {string} targetBranch Target branch of commit
462
+ * @param {CommitFilter} filter Callback function that returns whether a
463
+ * commit/pull request matches certain criteria
464
+ * @param {number} maxResults Limit the number of results searched.
465
+ * Defaults to unlimited.
466
+ * @returns {Commit[]} List of commits to current branch
804
467
  * @throws {GitHubAPIError} on an API error
805
468
  */
806
- async lastMergedPRByHeadBranch(headBranch) {
807
- const baseBranch = await this.getDefaultBranch();
808
- const response = await this.graphqlRequest({
809
- query: `query lastMergedPRByHeadBranch($owner: String!, $repo: String!, $baseBranch: String!, $headBranch: String!) {
810
- repository(owner: $owner, name: $repo) {
811
- pullRequests(baseRefName: $baseBranch, states: MERGED, orderBy: {field: CREATED_AT, direction: DESC}, first: 1, headRefName: $headBranch) {
812
- nodes {
813
- title
814
- body
815
- number
816
- mergeCommit {
817
- oid
818
- }
819
- files(first: 100) {
820
- nodes {
821
- path
822
- }
823
- pageInfo {
824
- hasNextPage
825
- endCursor
826
- }
827
- }
828
- labels(first: 10) {
829
- nodes {
830
- name
831
- }
832
- }
833
- }
834
- }
835
- }
836
- }`,
837
- owner: this.owner,
838
- repo: this.repo,
839
- baseBranch,
840
- headBranch,
841
- });
842
- let result = undefined;
843
- const pr = response.repository.pullRequests.nodes[0];
844
- if (pr) {
845
- const files = pr.files.nodes.map(({ path }) => path);
846
- let hasMoreFiles = pr.files.pageInfo.hasNextPage;
847
- let cursor = pr.files.pageInfo.endCursor;
848
- while (hasMoreFiles) {
849
- const next = await this.pullRequestFiles(pr.number, cursor);
850
- const nextFiles = next.node.files.edges.map(fe => fe.node.path);
851
- files.push(...nextFiles);
852
- cursor = next.node.files.pageInfo.endCursor;
853
- hasMoreFiles = next.node.files.pageInfo.hasNextPage;
469
+ async commitsSince(targetBranch, filter, maxResults = Number.MAX_SAFE_INTEGER) {
470
+ const commits = [];
471
+ const generator = this.mergeCommitIterator(targetBranch, maxResults);
472
+ for await (const commit of generator) {
473
+ if (filter(commit)) {
474
+ break;
854
475
  }
855
- result = {
856
- sha: pr.mergeCommit.oid,
857
- title: pr.title,
858
- body: pr.body,
859
- number: pr.number,
860
- baseRefName: baseBranch,
861
- headRefName: headBranch,
862
- files,
863
- labels: pr.labels.nodes.map(({ name }) => name),
864
- };
476
+ commits.push(commit);
865
477
  }
866
- return result;
478
+ return commits;
867
479
  }
868
480
  /**
869
- * If we can't find a release branch (a common cause of this, as an example
870
- * is that we might be dealing with the first relese), use the last semver
871
- * tag that's available on the repository:
872
- *
873
- * TODO: it would be good to not need to maintain this logic, and the
874
- * logic that introspects version based on the prior release PR.
481
+ * Iterate through commit history with a max number of results scanned.
875
482
  *
876
- * @param {string} prefix If provided, filter the tags with this prefix
877
- * @param {boolean} preRelease Whether or not to include pre-releases
878
- * @return {GitHubTag|undefined}
879
- * @throws {GitHubAPIError} on an API error *
483
+ * @param {string} targetBranch target branch of commit
484
+ * @param {number} maxResults maxResults - Limit the number of results searched.
485
+ * Defaults to unlimited.
486
+ * @yields {Commit}
487
+ * @throws {GitHubAPIError} on an API error
880
488
  */
881
- async latestTagFallback(prefix, preRelease = false) {
882
- const tags = await this.allTags(prefix);
883
- const versions = Object.keys(tags).filter(t => {
884
- // remove any pre-releases from the list:
885
- return preRelease || !t.includes('-');
886
- });
887
- // no tags have been created yet.
888
- if (versions.length === 0)
889
- return undefined;
890
- // We use a slightly modified version of semver's sorting algorithm, which
891
- // prefixes the numeric part of a pre-release with '0's, so that
892
- // 010 is greater than > 002.
893
- versions.sort((v1, v2) => {
894
- if (v1.includes('-')) {
895
- const [prefix, suffix] = v1.split('-');
896
- v1 = prefix + '-' + suffix.replace(/[a-zA-Z.]/, '').padStart(6, '0');
489
+ async *mergeCommitIterator(targetBranch, maxResults = Number.MAX_SAFE_INTEGER) {
490
+ let cursor = undefined;
491
+ let results = 0;
492
+ while (results < maxResults) {
493
+ const response = await this.mergeCommitsGraphQL(targetBranch, cursor);
494
+ // no response usually means that the branch can't be found
495
+ if (!response) {
496
+ break;
497
+ }
498
+ for (let i = 0; i < response.data.length; i++) {
499
+ results += 1;
500
+ yield response.data[i];
897
501
  }
898
- if (v2.includes('-')) {
899
- const [prefix, suffix] = v2.split('-');
900
- v2 = prefix + '-' + suffix.replace(/[a-zA-Z.]/, '').padStart(6, '0');
502
+ if (!response.pageInfo.hasNextPage) {
503
+ break;
901
504
  }
902
- return semver.rcompare(v1, v2);
903
- });
904
- return {
905
- name: tags[versions[0]].name,
906
- sha: tags[versions[0]].sha,
907
- version: tags[versions[0]].version,
908
- };
505
+ cursor = response.pageInfo.endCursor;
506
+ }
909
507
  }
910
- async mergeCommitsGraphQL(cursor) {
911
- const targetBranch = await this.getDefaultBranch();
508
+ async mergeCommitsGraphQL(targetBranch, cursor) {
509
+ logger_1.logger.debug(`Fetching merge commits on branch ${targetBranch} with cursor: ${cursor}`);
912
510
  const response = await this.graphqlRequest({
913
- query: `query pullRequestsSince($owner: String!, $repo: String!, $num: Int!, $targetBranch: String!, $cursor: String) {
511
+ query: `query pullRequestsSince($owner: String!, $repo: String!, $num: Int!, $maxFilesChanged: Int, $targetBranch: String!, $cursor: String) {
914
512
  repository(owner: $owner, name: $repo) {
915
513
  ref(qualifiedName: $targetBranch) {
916
514
  target {
@@ -932,6 +530,15 @@ class GitHub {
932
530
  mergeCommit {
933
531
  oid
934
532
  }
533
+ files(first: $maxFilesChanged) {
534
+ nodes {
535
+ path
536
+ }
537
+ pageInfo {
538
+ endCursor
539
+ hasNextPage
540
+ }
541
+ }
935
542
  }
936
543
  }
937
544
  sha: oid
@@ -948,10 +555,11 @@ class GitHub {
948
555
  }
949
556
  }`,
950
557
  cursor,
951
- owner: this.owner,
952
- repo: this.repo,
558
+ owner: this.repository.owner,
559
+ repo: this.repository.repo,
953
560
  num: 25,
954
561
  targetBranch,
562
+ maxFilesChanged: 64,
955
563
  });
956
564
  // if the branch does exist, return null
957
565
  if (!response.repository.ref) {
@@ -972,59 +580,42 @@ class GitHub {
972
580
  return pr.mergeCommit && pr.mergeCommit.oid === graphCommit.sha;
973
581
  });
974
582
  if (pullRequest) {
975
- return {
976
- commit,
977
- pullRequest: {
978
- sha: commit.sha,
979
- number: pullRequest.number,
980
- baseRefName: pullRequest.baseRefName,
981
- headRefName: pullRequest.headRefName,
982
- title: pullRequest.title,
983
- body: pullRequest.body,
984
- labels: pullRequest.labels.nodes.map(node => node.name),
985
- },
583
+ const files = pullRequest.files.nodes.map(node => node.path);
584
+ commit.pullRequest = {
585
+ sha: commit.sha,
586
+ number: pullRequest.number,
587
+ baseBranchName: pullRequest.baseRefName,
588
+ headBranchName: pullRequest.headRefName,
589
+ title: pullRequest.title,
590
+ body: pullRequest.body,
591
+ labels: pullRequest.labels.nodes.map(node => node.name),
592
+ files,
986
593
  };
594
+ // We cannot directly fetch files on commits via graphql, only provide file
595
+ // information for commits with associated pull requests
596
+ commit.files = files;
987
597
  }
988
- return {
989
- commit,
990
- };
598
+ else {
599
+ logger_1.logger.warn(`No merged pull request for commit: ${graphCommit.sha} - files unavailable`);
600
+ }
601
+ return commit;
991
602
  }),
992
603
  };
993
604
  }
994
605
  /**
995
- * Search through commit history to find the latest commit that matches to
996
- * provided filter.
997
- *
998
- * @param {CommitFilter} filter - Callback function that returns whether a
999
- * commit/pull request matches certain criteria
1000
- * @param {number} maxResults - Limit the number of results searched.
1001
- * Defaults to unlimited.
1002
- * @returns {CommitWithPullRequest}
1003
- * @throws {GitHubAPIError} on an API error
1004
- */
1005
- async findMergeCommit(filter, maxResults = Number.MAX_SAFE_INTEGER) {
1006
- const generator = this.mergeCommitIterator(maxResults);
1007
- for await (const commitWithPullRequest of generator) {
1008
- if (filter(commitWithPullRequest.commit, commitWithPullRequest.pullRequest)) {
1009
- return commitWithPullRequest;
1010
- }
1011
- }
1012
- return undefined;
1013
- }
1014
- /**
1015
- * Iterate through commit history with a max number of results scanned.
606
+ * Iterate through merged pull requests with a max number of results scanned.
1016
607
  *
1017
- * @param maxResults {number} maxResults - Limit the number of results searched.
608
+ * @param {number} maxResults maxResults - Limit the number of results searched.
1018
609
  * Defaults to unlimited.
1019
- * @yields {CommitWithPullRequest}
610
+ * @yields {PullRequest}
1020
611
  * @throws {GitHubAPIError} on an API error
1021
612
  */
1022
- async *mergeCommitIterator(maxResults = Number.MAX_SAFE_INTEGER) {
613
+ async *pullRequestIterator(targetBranch, status = 'MERGED', maxResults = Number.MAX_SAFE_INTEGER) {
1023
614
  let cursor = undefined;
1024
615
  let results = 0;
1025
616
  while (results < maxResults) {
1026
- const response = await this.mergeCommitsGraphQL(cursor);
1027
- // no response usually means that the branch can't be found
617
+ const response = await this.pullRequestsGraphQL(targetBranch, status, cursor);
618
+ // no response usually means we ran out of results
1028
619
  if (!response) {
1029
620
  break;
1030
621
  }
@@ -1039,194 +630,236 @@ class GitHub {
1039
630
  }
1040
631
  }
1041
632
  /**
1042
- * Iterate through merged pull requests with a max number of results scanned.
633
+ * Return a list of merged pull requests. The list is not guaranteed to be sorted
634
+ * by merged_at, but is generally most recent first.
1043
635
  *
1044
- * @param maxResults {number} maxResults - Limit the number of results searched.
1045
- * Defaults to unlimited.
1046
- * @yields {MergedGitHubPR}
636
+ * @param {string} targetBranch - Base branch of the pull request. Defaults to
637
+ * the configured default branch.
638
+ * @param {number} page - Page of results. Defaults to 1.
639
+ * @param {number} perPage - Number of results per page. Defaults to 100.
640
+ * @returns {PullRequestHistory | null} - List of merged pull requests
1047
641
  * @throws {GitHubAPIError} on an API error
1048
642
  */
1049
- async *mergedPullRequestIterator(branch, maxResults = Number.MAX_SAFE_INTEGER) {
1050
- let page = 1;
1051
- const results = 0;
1052
- while (results < maxResults) {
1053
- const pullRequests = await this.findMergedPullRequests(branch, page);
1054
- // no response usually means we ran out of results
1055
- if (pullRequests.length === 0) {
1056
- break;
1057
- }
1058
- for (let i = 0; i < pullRequests.length; i++) {
1059
- yield pullRequests[i];
643
+ async pullRequestsGraphQL(targetBranch, states = 'MERGED', cursor) {
644
+ logger_1.logger.debug(`Fetching ${states} pull requests on branch ${targetBranch} with cursor ${cursor}`);
645
+ const response = await this.graphqlRequest({
646
+ query: `query mergedPullRequests($owner: String!, $repo: String!, $num: Int!, $maxFilesChanged: Int, $targetBranch: String!, $states: [PullRequestState!], $cursor: String) {
647
+ repository(owner: $owner, name: $repo) {
648
+ pullRequests(first: $num, after: $cursor, baseRefName: $targetBranch, states: $states, orderBy: {field: CREATED_AT, direction: DESC}) {
649
+ nodes {
650
+ number
651
+ title
652
+ baseRefName
653
+ headRefName
654
+ labels(first: 10) {
655
+ nodes {
656
+ name
657
+ }
658
+ }
659
+ body
660
+ mergeCommit {
661
+ oid
662
+ }
663
+ files(first: $maxFilesChanged) {
664
+ nodes {
665
+ path
666
+ }
667
+ pageInfo {
668
+ endCursor
669
+ hasNextPage
670
+ }
671
+ }
672
+ }
673
+ pageInfo {
674
+ endCursor
675
+ hasNextPage
676
+ }
1060
677
  }
1061
- page += 1;
678
+ }
679
+ }`,
680
+ cursor,
681
+ owner: this.repository.owner,
682
+ repo: this.repository.repo,
683
+ num: 25,
684
+ targetBranch,
685
+ states,
686
+ maxFilesChanged: 64,
687
+ });
688
+ if (!response.repository.pullRequests) {
689
+ logger_1.logger.warn(`Could not find merged pull requests for branch ${targetBranch} - it likely does not exist.`);
690
+ return null;
1062
691
  }
692
+ const pullRequests = (response.repository.pullRequests.nodes ||
693
+ []);
694
+ return {
695
+ pageInfo: response.repository.pullRequests.pageInfo,
696
+ data: pullRequests.map(pullRequest => {
697
+ var _a;
698
+ return {
699
+ sha: (_a = pullRequest.mergeCommit) === null || _a === void 0 ? void 0 : _a.oid,
700
+ number: pullRequest.number,
701
+ baseBranchName: pullRequest.baseRefName,
702
+ headBranchName: pullRequest.headRefName,
703
+ labels: (pullRequest.labels.nodes || []).map(l => l.name),
704
+ title: pullRequest.title,
705
+ body: pullRequest.body + '',
706
+ files: pullRequest.files.nodes.map(node => node.path),
707
+ };
708
+ }),
709
+ };
1063
710
  }
1064
711
  /**
1065
- * Returns the list of commits to the default branch after the provided filter
1066
- * query has been satified.
712
+ * Iterate through merged pull requests with a max number of results scanned.
1067
713
  *
1068
- * @param {CommitFilter} filter - Callback function that returns whether a
1069
- * commit/pull request matches certain criteria
1070
- * @param {number} maxResults - Limit the number of results searched.
714
+ * @param {number} maxResults maxResults - Limit the number of results searched.
1071
715
  * Defaults to unlimited.
1072
- * @returns {Commit[]} - List of commits to current branch
716
+ * @yields {GitHubRelease}
1073
717
  * @throws {GitHubAPIError} on an API error
1074
718
  */
1075
- async commitsSince(filter, maxResults = Number.MAX_SAFE_INTEGER) {
1076
- const commits = [];
1077
- const generator = this.mergeCommitIterator(maxResults);
1078
- for await (const commitWithPullRequest of generator) {
1079
- if (filter(commitWithPullRequest.commit, commitWithPullRequest.pullRequest)) {
719
+ async *releaseIterator(maxResults = Number.MAX_SAFE_INTEGER) {
720
+ let results = 0;
721
+ let cursor = undefined;
722
+ while (results < maxResults) {
723
+ const response = await this.releaseGraphQL(cursor);
724
+ if (!response) {
1080
725
  break;
1081
726
  }
1082
- commits.push(commitWithPullRequest.commit);
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;
1083
735
  }
1084
- return commits;
1085
736
  }
1086
- /**
1087
- * Helper to find the first merged pull request that matches the
1088
- * given criteria. The helper will paginate over all pull requests
1089
- * merged into the specified target branch.
1090
- *
1091
- * @param {string} targetBranch - Base branch of the pull request
1092
- * @param {MergedPullRequestFilter} filter - Callback function that
1093
- * returns whether a pull request matches certain criteria
1094
- * @param {number} maxResults - Limit the number of results searched.
1095
- * Defaults to unlimited.
1096
- * @returns {MergedGitHubPR | undefined} - Returns the first matching
1097
- * pull request, or `undefined` if no matching pull request found.
1098
- * @throws {GitHubAPIError} on an API error
1099
- */
1100
- async findMergedPullRequest(targetBranch, filter, maxResults = Number.MAX_SAFE_INTEGER) {
1101
- const generator = this.mergedPullRequestIterator(targetBranch, maxResults);
1102
- for await (const mergedPullRequest of generator) {
1103
- if (filter(mergedPullRequest)) {
1104
- return mergedPullRequest;
737
+ async releaseGraphQL(cursor) {
738
+ logger_1.logger.debug(`Fetching releases with cursor ${cursor}`);
739
+ const response = await this.graphqlRequest({
740
+ query: `query releases($owner: String!, $repo: String!, $num: Int!, $cursor: String) {
741
+ repository(owner: $owner, name: $repo) {
742
+ releases(first: $num, after: $cursor, orderBy: {field: CREATED_AT, direction: DESC}) {
743
+ nodes {
744
+ name
745
+ tag {
746
+ name
747
+ }
748
+ tagCommit {
749
+ oid
750
+ }
751
+ url
752
+ description
1105
753
  }
754
+ pageInfo {
755
+ endCursor
756
+ hasNextPage
757
+ }
758
+ }
1106
759
  }
1107
- return undefined;
760
+ }`,
761
+ cursor,
762
+ owner: this.repository.owner,
763
+ repo: this.repository.repo,
764
+ num: 25,
765
+ });
766
+ if (!response.repository.releases) {
767
+ logger_1.logger.warn('Could not find releases.');
768
+ return null;
769
+ }
770
+ const releases = (response.repository.releases.nodes ||
771
+ []);
772
+ return {
773
+ pageInfo: response.repository.releases.pageInfo,
774
+ data: releases
775
+ .filter(release => !!release.tagCommit)
776
+ .map(release => {
777
+ if (!release.tag || !release.tagCommit) {
778
+ logger_1.logger.debug(release);
779
+ }
780
+ return {
781
+ name: release.name || undefined,
782
+ tagName: release.tag ? release.tag.name : 'unknown',
783
+ sha: release.tagCommit.oid,
784
+ notes: release.description,
785
+ url: release.url,
786
+ };
787
+ }),
788
+ };
1108
789
  }
1109
790
  /**
1110
- * Find the last merged pull request that targeted the default
1111
- * branch and looks like a release PR.
791
+ * Fetch the contents of a file from the configured branch
1112
792
  *
1113
- * Note: The default matcher will rule out pre-releases.
793
+ * @param {string} path The path to the file in the repository
794
+ * @returns {GitHubFileContents}
795
+ * @throws {GitHubAPIError} on other API errors
796
+ */
797
+ async getFileContents(path) {
798
+ return await this.getFileContentsOnBranch(path, this.repository.defaultBranch);
799
+ }
800
+ /**
801
+ * Fetch the contents of a file
1114
802
  *
1115
- * @param {string[]} labels - If provided, ensure that the pull
1116
- * request has all of the specified labels
1117
- * @param {string|undefined} branchPrefix - If provided, limit
1118
- * release pull requests that contain the specified component
1119
- * @param {boolean} preRelease - Whether to include pre-release
1120
- * versions in the response. Defaults to true.
1121
- * @param {number} maxResults - Limit the number of results searched.
1122
- * Defaults to unlimited.
1123
- * @returns {MergedGitHubPR|undefined}
1124
- * @throws {GitHubAPIError} on an API error
803
+ * @param {string} path The path to the file in the repository
804
+ * @param {string} branch The branch to fetch from
805
+ * @returns {GitHubFileContents}
806
+ * @throws {GitHubAPIError} on other API errors
1125
807
  */
1126
- async findMergedReleasePR(labels, branchPrefix = undefined, preRelease = true, maxResults = Number.MAX_SAFE_INTEGER) {
1127
- branchPrefix = (branchPrefix === null || branchPrefix === void 0 ? void 0 : branchPrefix.endsWith('-')) ? branchPrefix.replace(/-$/, '')
1128
- : branchPrefix;
1129
- const targetBranch = await this.getDefaultBranch();
1130
- const mergedReleasePullRequest = await this.findMergedPullRequest(targetBranch, mergedPullRequest => {
1131
- // If labels specified, ensure the pull request has all the specified labels
1132
- if (labels.length > 0 &&
1133
- !this.hasAllLabels(labels, mergedPullRequest.labels)) {
1134
- return false;
1135
- }
1136
- const branchName = branch_name_1.BranchName.parse(mergedPullRequest.headRefName);
1137
- if (!branchName) {
1138
- return false;
1139
- }
1140
- // If branchPrefix is specified, ensure it is found in the branch name.
1141
- // If branchPrefix is not specified, component should also be undefined.
1142
- if (branchName.getComponent() !== branchPrefix) {
1143
- return false;
1144
- }
1145
- // In this implementation we expect to have a release version
1146
- const version = branchName.getVersion();
1147
- if (!version) {
1148
- return false;
1149
- }
1150
- // What's left by now should just be the version string.
1151
- // Check for pre-releases if needed.
1152
- if (!preRelease && version.indexOf('-') >= 0) {
1153
- return false;
1154
- }
1155
- // Make sure we did get a valid semver.
1156
- const normalizedVersion = semver.valid(version);
1157
- if (!normalizedVersion) {
1158
- return false;
808
+ async getFileContentsOnBranch(path, branch) {
809
+ logger_1.logger.debug(`Fetching ${path} from branch ${branch}`);
810
+ try {
811
+ return await this.getFileContentsWithSimpleAPI(path, branch);
812
+ }
813
+ catch (err) {
814
+ if (err.status === 403) {
815
+ return await this.getFileContentsWithDataAPI(path, branch);
1159
816
  }
1160
- return true;
1161
- }, maxResults);
1162
- return mergedReleasePullRequest;
817
+ throw err;
818
+ }
1163
819
  }
1164
- hasAllLabels(labelsA, labelsB) {
1165
- let hasAll = true;
1166
- labelsA.forEach(label => {
1167
- if (labelsB.indexOf(label) === -1)
1168
- hasAll = false;
1169
- });
1170
- return hasAll;
820
+ async getFileJson(path, branch) {
821
+ const content = await this.getFileContentsOnBranch(path, branch);
822
+ return JSON.parse(content.parsedContent);
1171
823
  }
1172
824
  /**
1173
- * Find open pull requests with matching labels.
825
+ * Returns a list of paths to all files with a given name.
1174
826
  *
1175
- * @param {string[]} labels List of labels to match
1176
- * @param {number} perPage Optional. Defaults to 100
1177
- * @return {PullsListResponseItems} Pull requests
827
+ * If a prefix is specified, only return paths that match
828
+ * the provided prefix.
829
+ *
830
+ * @param filename The name of the file to find
831
+ * @param prefix Optional path prefix used to filter results
832
+ * @returns {string[]} List of file paths
1178
833
  * @throws {GitHubAPIError} on an API error
1179
834
  */
1180
- async findOpenReleasePRs(labels, perPage = 100) {
1181
- const baseLabel = await this.getBaseLabel();
1182
- const openReleasePRs = [];
1183
- const pullsResponse = (await this.request(`GET /repos/:owner/:repo/pulls?state=open&per_page=${perPage}`, {
1184
- owner: this.owner,
1185
- repo: this.repo,
1186
- }));
1187
- for (const pull of pullsResponse.data) {
1188
- // Verify that this PR was based against our base branch of interest.
1189
- if (!pull.base || pull.base.label !== baseLabel)
1190
- continue;
1191
- let hasAllLabels = false;
1192
- const observedLabels = pull.labels.map(l => l.name);
1193
- for (const expectedLabel of labels) {
1194
- if (observedLabels.includes(expectedLabel)) {
1195
- hasAllLabels = true;
1196
- }
1197
- else {
1198
- hasAllLabels = false;
1199
- break;
1200
- }
1201
- }
1202
- if (hasAllLabels)
1203
- openReleasePRs.push(pull);
1204
- }
1205
- return openReleasePRs;
835
+ async findFilesByFilename(filename, prefix) {
836
+ return this.findFilesByFilenameAndRef(filename, this.repository.defaultBranch, prefix);
1206
837
  }
1207
838
  /**
1208
- * Add labels to an issue or pull request
839
+ * Open a pull request
1209
840
  *
1210
- * @param {string[]} labels List of labels to add
1211
- * @param {number} pr Issue or pull request number
1212
- * @return {boolean} Whether or not the labels were added
841
+ * @param {ReleasePullRequest} releasePullRequest Pull request data to update
842
+ * @param {string} targetBranch The base branch of the pull request
843
+ * @param {GitHubPR} options The pull request options
1213
844
  * @throws {GitHubAPIError} on an API error
1214
845
  */
1215
- async addLabels(labels, pr) {
1216
- // If the PR is being created from a fork, it will not have permission
1217
- // to add and remove labels from the PR:
1218
- if (this.fork) {
1219
- logger_1.logger.warn('release labels were not added, due to PR being created from fork');
1220
- return false;
846
+ async createReleasePullRequest(releasePullRequest, targetBranch, options) {
847
+ let message = releasePullRequest.title.toString();
848
+ if (options === null || options === void 0 ? void 0 : options.signoffUser) {
849
+ message = signoff_commit_message_1.signoffCommitMessage(message, options.signoffUser);
1221
850
  }
1222
- logger_1.logger.info(`adding label ${chalk.green(labels.join(','))} to https://github.com/${this.owner}/${this.repo}/pull/${pr}`);
1223
- await this.request('POST /repos/:owner/:repo/issues/:issue_number/labels', {
1224
- owner: this.owner,
1225
- repo: this.repo,
1226
- issue_number: pr,
1227
- labels,
851
+ return await this.createPullRequest({
852
+ headBranchName: releasePullRequest.headRefName,
853
+ baseBranchName: targetBranch,
854
+ number: -1,
855
+ title: releasePullRequest.title.toString(),
856
+ body: releasePullRequest.body.toString().slice(0, MAX_ISSUE_BODY_SIZE),
857
+ labels: releasePullRequest.labels,
858
+ files: [],
859
+ }, targetBranch, message, releasePullRequest.updates, {
860
+ fork: options === null || options === void 0 ? void 0 : options.fork,
861
+ draft: releasePullRequest.draft,
1228
862
  });
1229
- return true;
1230
863
  }
1231
864
  /**
1232
865
  * Given a set of proposed updates, build a changeset to suggest.
@@ -1241,10 +874,10 @@ class GitHub {
1241
874
  for (const update of updates) {
1242
875
  let content;
1243
876
  try {
1244
- if (update.contents) {
877
+ if (update.cachedFileContents) {
1245
878
  // we already loaded the file contents earlier, let's not
1246
879
  // hit GitHub again.
1247
- content = { data: update.contents };
880
+ content = { data: update.cachedFileContents };
1248
881
  }
1249
882
  else {
1250
883
  const fileContent = await this.getFileContentsOnBranch(update.path, defaultBranch);
@@ -1256,15 +889,15 @@ class GitHub {
1256
889
  throw err;
1257
890
  // if the file is missing and create = false, just continue
1258
891
  // to the next update, otherwise create the file.
1259
- if (!update.create) {
1260
- logger_1.logger.warn(`file ${chalk.green(update.path)} did not exist`);
892
+ if (!update.createIfMissing) {
893
+ logger_1.logger.warn(`file ${update.path} did not exist`);
1261
894
  continue;
1262
895
  }
1263
896
  }
1264
897
  const contentText = content
1265
898
  ? Buffer.from(content.data.content, 'base64').toString('utf8')
1266
899
  : undefined;
1267
- const updatedContent = update.updateContent(contentText);
900
+ const updatedContent = update.updater.updateContent(contentText);
1268
901
  if (updatedContent) {
1269
902
  changes.set(update.path, {
1270
903
  content: updatedContent,
@@ -1274,92 +907,6 @@ class GitHub {
1274
907
  }
1275
908
  return changes;
1276
909
  }
1277
- // The base label is basically the default branch, attached to the owner.
1278
- async getBaseLabel() {
1279
- const baseBranch = await this.getDefaultBranch();
1280
- return `${this.owner}:${baseBranch}`;
1281
- }
1282
- /**
1283
- * Returns the branch we are targetting for releases. Defaults
1284
- * to the repository's default/primary branch.
1285
- *
1286
- * @returns {string}
1287
- * @throws {GitHubAPIError} on an API error
1288
- */
1289
- async getDefaultBranch() {
1290
- if (!this.defaultBranch) {
1291
- this.defaultBranch = await this.getRepositoryDefaultBranch();
1292
- }
1293
- return this.defaultBranch;
1294
- }
1295
- // Takes a potentially unqualified branch name, and turns it
1296
- // into a fully qualified ref.
1297
- //
1298
- // e.g. main -> refs/heads/main
1299
- static fullyQualifyBranchRef(refName) {
1300
- let final = refName;
1301
- if (final.indexOf('/') < 0) {
1302
- final = `refs/heads/${final}`;
1303
- }
1304
- return final;
1305
- }
1306
- /**
1307
- * Fetch the contents of a file using the Git data API
1308
- *
1309
- * @param {string} path The path to the file in the repository
1310
- * @param {string} branch The branch to fetch from
1311
- * @returns {GitHubFileContents}
1312
- * @throws {GitHubAPIError} on other API errors
1313
- */
1314
- async getFileContentsWithDataAPI(path, branch) {
1315
- const options = {
1316
- owner: this.owner,
1317
- repo: this.repo,
1318
- branch,
1319
- };
1320
- const repoTree = await this.request('GET /repos/:owner/:repo/git/trees/:branch', options);
1321
- const blobDescriptor = repoTree.data.tree.find(tree => tree.path === path);
1322
- if (!blobDescriptor) {
1323
- throw new Error(`Could not find requested path: ${path}`);
1324
- }
1325
- const resp = await this.request('GET /repos/:owner/:repo/git/blobs/:sha', {
1326
- owner: this.owner,
1327
- repo: this.repo,
1328
- sha: blobDescriptor.sha,
1329
- });
1330
- return {
1331
- parsedContent: Buffer.from(resp.data.content, 'base64').toString('utf8'),
1332
- content: resp.data.content,
1333
- sha: resp.data.sha,
1334
- };
1335
- }
1336
- /**
1337
- * Fetch the contents of a file from the configured branch
1338
- *
1339
- * @param {string} path The path to the file in the repository
1340
- * @returns {GitHubFileContents}
1341
- * @throws {GitHubAPIError} on other API errors
1342
- */
1343
- async getFileContents(path) {
1344
- return await this.getFileContentsOnBranch(path, await this.getDefaultBranch());
1345
- }
1346
- normalizePrefix(prefix) {
1347
- return prefix.replace(/^[/\\]/, '').replace(/[/\\]$/, '');
1348
- }
1349
- /**
1350
- * Returns a list of paths to all files with a given name.
1351
- *
1352
- * If a prefix is specified, only return paths that match
1353
- * the provided prefix.
1354
- *
1355
- * @param filename The name of the file to find
1356
- * @param prefix Optional path prefix used to filter results
1357
- * @returns {string[]} List of file paths
1358
- * @throws {GitHubAPIError} on an API error
1359
- */
1360
- async findFilesByFilename(filename, prefix) {
1361
- return this.findFilesByFilenameAndRef(filename, await this.getDefaultBranch(), prefix);
1362
- }
1363
910
  /**
1364
911
  * Returns a list of paths to all files with a given file
1365
912
  * extension.
@@ -1374,10 +921,34 @@ class GitHub {
1374
921
  * @throws {GitHubAPIError} on an API error
1375
922
  */
1376
923
  async findFilesByExtension(extension, prefix) {
1377
- return this.findFilesByExtensionAndRef(extension, await this.getDefaultBranch(), prefix);
924
+ return this.findFilesByExtensionAndRef(extension, this.repository.defaultBranch, prefix);
1378
925
  }
1379
926
  }
1380
927
  exports.GitHub = GitHub;
928
+ // Takes a potentially unqualified branch name, and turns it
929
+ // into a fully qualified ref.
930
+ //
931
+ // e.g. main -> refs/heads/main
932
+ function fullyQualifyBranchRef(refName) {
933
+ let final = refName;
934
+ if (final.indexOf('/') < 0) {
935
+ final = `refs/heads/${final}`;
936
+ }
937
+ return final;
938
+ }
939
+ /**
940
+ * Normalize a provided prefix by removing leading and trailing
941
+ * slashes.
942
+ *
943
+ * @param prefix String to normalize
944
+ */
945
+ function normalizePrefix(prefix) {
946
+ const normalized = prefix.replace(/^[/\\]/, '').replace(/[/\\]$/, '');
947
+ if (normalized === manifest_1.ROOT_PROJECT_PATH) {
948
+ return '';
949
+ }
950
+ return normalized;
951
+ }
1381
952
  /**
1382
953
  * Wrap an async method with error handling
1383
954
  *