release-please 12.6.0 → 13.0.0-candidate.3

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