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
@@ -13,545 +13,641 @@
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.Manifest = void 0;
17
- const commit_split_1 = require("./commit-split");
18
- const constants_1 = require("./constants");
16
+ exports.Manifest = exports.MANIFEST_PULL_REQUEST_TITLE_PATTERN = exports.ROOT_PROJECT_PATH = exports.DEFAULT_RELEASE_PLEASE_MANIFEST = exports.DEFAULT_RELEASE_PLEASE_CONFIG = void 0;
17
+ const version_1 = require("./version");
18
+ const logger_1 = require("./util/logger");
19
+ const commit_split_1 = require("./util/commit-split");
20
+ const tag_name_1 = require("./util/tag-name");
19
21
  const branch_name_1 = require("./util/branch-name");
20
- const _1 = require(".");
22
+ const pull_request_title_1 = require("./util/pull-request-title");
23
+ const factory_1 = require("./factory");
24
+ const pull_request_body_1 = require("./util/pull-request-body");
25
+ const merge_1 = require("./plugins/merge");
21
26
  const release_please_manifest_1 = require("./updaters/release-please-manifest");
22
- const checkpoint_1 = require("./util/checkpoint");
23
- const github_release_1 = require("./github-release");
24
- const plugins_1 = require("./plugins");
25
- const signoff_commit_message_1 = require("./util/signoff-commit-message");
27
+ exports.DEFAULT_RELEASE_PLEASE_CONFIG = 'release-please-config.json';
28
+ exports.DEFAULT_RELEASE_PLEASE_MANIFEST = '.release-please-manifest.json';
29
+ exports.ROOT_PROJECT_PATH = '.';
30
+ const DEFAULT_COMPONENT_NAME = '';
31
+ const DEFAULT_LABELS = ['autorelease: pending'];
32
+ const DEFAULT_RELEASE_LABELS = ['autorelease: tagged'];
33
+ exports.MANIFEST_PULL_REQUEST_TITLE_PATTERN = 'chore: release ${branch}';
26
34
  class Manifest {
27
- constructor(options) {
28
- this.gh = options.github;
29
- this.configFileName = options.configFile || constants_1.RELEASE_PLEASE_CONFIG;
30
- this.manifestFileName = options.manifestFile || constants_1.RELEASE_PLEASE_MANIFEST;
31
- this.checkpoint = options.checkpoint || checkpoint_1.checkpoint;
32
- this.signoff = options.signoff;
35
+ /**
36
+ * Create a Manifest from explicit config in code. This assumes that the
37
+ * repository has a single component at the root path.
38
+ *
39
+ * @param {GitHub} github GitHub client
40
+ * @param {string} targetBranch The releaseable base branch
41
+ * @param {RepositoryConfig} repositoryConfig Parsed configuration of path => release configuration
42
+ * @param {ReleasedVersions} releasedVersions Parsed versions of path => latest release version
43
+ * @param {ManifestOptions} manifestOptions Optional. Manifest options
44
+ * @param {string} manifestOptions.bootstrapSha If provided, use this SHA
45
+ * as the point to consider commits after
46
+ * @param {boolean} manifestOptions.alwaysLinkLocal Option for the node-workspace
47
+ * plugin
48
+ * @param {boolean} manifestOptions.separatePullRequests If true, create separate pull
49
+ * requests instead of a single manifest release pull request
50
+ * @param {PluginType[]} manifestOptions.plugins Any plugins to use for this repository
51
+ * @param {boolean} manifestOptions.fork If true, create pull requests from a fork. Defaults
52
+ * to `false`
53
+ * @param {string} manifestOptions.signoff Add a Signed-off-by annotation to the commit
54
+ * @param {string} manifestOptions.manifestPath Path to the versions manifest
55
+ * @param {string[]} manifestOptions.labels Labels that denote a pending, untagged release
56
+ * pull request. Defaults to `[autorelease: pending]`
57
+ * @param {string[]} manifestOptions.releaseLabels Labels to apply to a tagged release
58
+ * pull request. Defaults to `[autorelease: tagged]`
59
+ */
60
+ constructor(github, targetBranch, repositoryConfig, releasedVersions, manifestOptions) {
61
+ this.repository = github.repository;
62
+ this.github = github;
63
+ this.targetBranch = targetBranch;
64
+ this.repositoryConfig = repositoryConfig;
65
+ this.releasedVersions = releasedVersions;
66
+ this.manifestPath =
67
+ (manifestOptions === null || manifestOptions === void 0 ? void 0 : manifestOptions.manifestPath) || exports.DEFAULT_RELEASE_PLEASE_MANIFEST;
68
+ this.separatePullRequests = (manifestOptions === null || manifestOptions === void 0 ? void 0 : manifestOptions.separatePullRequests) || false;
69
+ this.plugins = (manifestOptions === null || manifestOptions === void 0 ? void 0 : manifestOptions.plugins) || [];
70
+ this.fork = (manifestOptions === null || manifestOptions === void 0 ? void 0 : manifestOptions.fork) || false;
71
+ this.signoffUser = manifestOptions === null || manifestOptions === void 0 ? void 0 : manifestOptions.signoff;
72
+ this.releaseLabels =
73
+ (manifestOptions === null || manifestOptions === void 0 ? void 0 : manifestOptions.releaseLabels) || DEFAULT_RELEASE_LABELS;
74
+ this.labels = (manifestOptions === null || manifestOptions === void 0 ? void 0 : manifestOptions.labels) || DEFAULT_LABELS;
75
+ this.bootstrapSha = manifestOptions === null || manifestOptions === void 0 ? void 0 : manifestOptions.bootstrapSha;
76
+ this.lastReleaseSha = manifestOptions === null || manifestOptions === void 0 ? void 0 : manifestOptions.lastReleaseSha;
33
77
  }
34
- async getBranchName() {
35
- return branch_name_1.BranchName.ofTargetBranch(await this.gh.getDefaultBranch());
78
+ /**
79
+ * Create a Manifest from config files in the repository.
80
+ *
81
+ * @param {GitHub} github GitHub client
82
+ * @param {string} targetBranch The releaseable base branch
83
+ * @param {string} configFile Optional. The path to the manifest config file
84
+ * @param {string} manifestFile Optional. The path to the manifest versions file
85
+ * @returns {Manifest}
86
+ */
87
+ static async fromManifest(github, targetBranch, configFile = exports.DEFAULT_RELEASE_PLEASE_CONFIG, manifestFile = exports.DEFAULT_RELEASE_PLEASE_MANIFEST, manifestOptionOverrides = {}) {
88
+ const [{ config: repositoryConfig, options: manifestOptions }, releasedVersions,] = await Promise.all([
89
+ parseConfig(github, configFile, targetBranch),
90
+ parseReleasedVersions(github, manifestFile, targetBranch),
91
+ ]);
92
+ return new Manifest(github, targetBranch, repositoryConfig, releasedVersions, { ...manifestOptions, ...manifestOptionOverrides });
36
93
  }
37
- async getFileJson(fileName, sha) {
38
- let content;
39
- try {
40
- if (sha) {
41
- content = await this.gh.getFileContentsWithSimpleAPI(fileName, sha, false);
42
- }
43
- else {
44
- content = await this.gh.getFileContents(fileName);
45
- }
46
- }
47
- catch (e) {
48
- this.checkpoint(`Failed to get ${fileName} at ${sha !== null && sha !== void 0 ? sha : 'HEAD'}: ${e.status}`, checkpoint_1.CheckpointType.Failure);
49
- // If a sha is provided this is a request for the manifest file at the
50
- // last merged Release PR. The only reason it would not exist is if a user
51
- // checkedout that branch and deleted the manifest file right before
52
- // merging. There is no recovery from that so we'll fall back to using
53
- // the manifest at the tip of the defaultBranch.
54
- if (sha === undefined) {
55
- // !sha means this is a request against the tip of the defaultBranch and
56
- // we require that the manifest and config exist there. If they don't,
57
- // they can be added and this exception will not be thrown.
58
- throw e;
59
- }
60
- return;
94
+ /**
95
+ * Create a Manifest from explicit config in code. This assumes that the
96
+ * repository has a single component at the root path.
97
+ *
98
+ * @param {GitHub} github GitHub client
99
+ * @param {string} targetBranch The releaseable base branch
100
+ * @param {ReleaserConfig} config Release strategy options
101
+ * @param {ManifestOptions} manifestOptions Optional. Manifest options
102
+ * @param {string} manifestOptions.bootstrapSha If provided, use this SHA
103
+ * as the point to consider commits after
104
+ * @param {boolean} manifestOptions.alwaysLinkLocal Option for the node-workspace
105
+ * plugin
106
+ * @param {boolean} manifestOptions.separatePullRequests If true, create separate pull
107
+ * requests instead of a single manifest release pull request
108
+ * @param {PluginType[]} manifestOptions.plugins Any plugins to use for this repository
109
+ * @param {boolean} manifestOptions.fork If true, create pull requests from a fork. Defaults
110
+ * to `false`
111
+ * @param {string} manifestOptions.signoff Add a Signed-off-by annotation to the commit
112
+ * @param {string} manifestOptions.manifestPath Path to the versions manifest
113
+ * @param {string[]} manifestOptions.labels Labels that denote a pending, untagged release
114
+ * pull request. Defaults to `[autorelease: pending]`
115
+ * @param {string[]} manifestOptions.releaseLabels Labels to apply to a tagged release
116
+ * pull request. Defaults to `[autorelease: tagged]`
117
+ * @returns {Manifest}
118
+ */
119
+ static async fromConfig(github, targetBranch, config, manifestOptions, path = exports.ROOT_PROJECT_PATH) {
120
+ const repositoryConfig = {};
121
+ repositoryConfig[path] = config;
122
+ const strategy = await factory_1.buildStrategy({
123
+ github,
124
+ ...config,
125
+ });
126
+ const component = await strategy.getComponent();
127
+ const releasedVersions = {};
128
+ const latestVersion = await latestReleaseVersion(github, targetBranch, component);
129
+ if (latestVersion) {
130
+ releasedVersions[path] = latestVersion;
61
131
  }
62
- return JSON.parse(content.parsedContent);
132
+ return new Manifest(github, targetBranch, repositoryConfig, releasedVersions, manifestOptions);
63
133
  }
64
- async getManifestJson(sha) {
65
- // cache headManifest since it's loaded in validate() as well as later on
66
- // and we never write to it.
67
- let manifest;
68
- if (sha === undefined) {
69
- if (!this.headManifest) {
70
- this.headManifest = await this.getFileJson(this.manifestFileName);
134
+ /**
135
+ * Build all candidate pull requests for this repository.
136
+ *
137
+ * Iterates through each path and builds a candidate pull request for component.
138
+ * Applies any configured plugins.
139
+ *
140
+ * @returns {ReleasePullRequest[]} The candidate pull requests to open or update.
141
+ */
142
+ async buildPullRequests() {
143
+ logger_1.logger.info('Building pull requests');
144
+ const pathsByComponent = await this.getPathsByComponent();
145
+ const strategiesByPath = await this.getStrategiesByPath();
146
+ // Collect all the SHAs of the latest release packages
147
+ logger_1.logger.info('Collecting release commit SHAs');
148
+ let releasesFound = 0;
149
+ const expectedReleases = Object.keys(strategiesByPath).length;
150
+ // SHAs by path
151
+ const releaseShasByPath = {};
152
+ // Releases by path
153
+ const releasesByPath = {};
154
+ for await (const release of this.github.releaseIterator(400)) {
155
+ // logger.debug(release);
156
+ const tagName = tag_name_1.TagName.parse(release.tagName);
157
+ if (!tagName) {
158
+ logger_1.logger.warn(`Unable to parse release name: ${release.name}`);
159
+ continue;
71
160
  }
72
- manifest = this.headManifest;
73
- }
74
- else {
75
- manifest = await this.getFileJson(this.manifestFileName, sha);
76
- }
77
- return manifest;
78
- }
79
- async getManifestVersions(sha, newPaths) {
80
- let manifestJson;
81
- const defaultBranch = await this.gh.getDefaultBranch();
82
- const bootstrapMsg = `Bootstrapping from ${this.manifestFileName} ` +
83
- `at tip of ${defaultBranch}`;
84
- if (sha === undefined) {
85
- this.checkpoint(bootstrapMsg, checkpoint_1.CheckpointType.Failure);
86
- }
87
- if (sha === false) {
88
- this.checkpoint(`${bootstrapMsg} for missing paths [${newPaths.join(', ')}]`, checkpoint_1.CheckpointType.Failure);
89
- }
90
- let atSha = 'tip';
91
- if (!sha) {
92
- manifestJson = await this.getManifestJson();
93
- }
94
- else {
95
- // try to retrieve manifest from last release sha.
96
- const maybeManifestJson = await this.getManifestJson(sha);
97
- atSha = sha;
98
- if (maybeManifestJson === undefined) {
99
- // user deleted manifest from last release PR before merging.
100
- this.checkpoint(bootstrapMsg, checkpoint_1.CheckpointType.Failure);
101
- manifestJson = await this.getManifestJson();
102
- atSha = 'tip';
161
+ const component = tagName.component || DEFAULT_COMPONENT_NAME;
162
+ const path = pathsByComponent[component];
163
+ if (!path) {
164
+ logger_1.logger.warn(`Found release tag with component '${component}', but not configured in manifest`);
165
+ continue;
103
166
  }
104
- else {
105
- manifestJson = maybeManifestJson;
167
+ const expectedVersion = this.releasedVersions[path];
168
+ if (!expectedVersion) {
169
+ logger_1.logger.warn(`Unable to find expected version for path '${path}' in manifest`);
170
+ continue;
106
171
  }
107
- }
108
- const parsed = new Map(Object.entries(manifestJson));
109
- if (sha === false) {
110
- return parsed;
111
- }
112
- else {
113
- return [parsed, atSha];
114
- }
115
- }
116
- async getConfigJson() {
117
- var _a, _b, _c, _d, _e, _f, _g, _h;
118
- // cache config since it's loaded in validate() as well as later on and we
119
- // never write to it.
120
- if (!this.configFile) {
121
- const config = await this.getFileJson(this.configFileName);
122
- const packages = [];
123
- for (const pkgPath in config.packages) {
124
- const pkgCfg = config.packages[pkgPath];
125
- const pkg = {
126
- path: pkgPath,
127
- releaseType: (_b = (_a = pkgCfg['release-type']) !== null && _a !== void 0 ? _a : config['release-type']) !== null && _b !== void 0 ? _b : 'node',
128
- packageName: pkgCfg['package-name'],
129
- bumpMinorPreMajor: (_c = pkgCfg['bump-minor-pre-major']) !== null && _c !== void 0 ? _c : config['bump-minor-pre-major'],
130
- bumpPatchForMinorPreMajor: (_d = pkgCfg['bump-patch-for-minor-pre-major']) !== null && _d !== void 0 ? _d : config['bump-patch-for-minor-pre-major'],
131
- changelogSections: (_e = pkgCfg['changelog-sections']) !== null && _e !== void 0 ? _e : config['changelog-sections'],
132
- changelogPath: pkgCfg['changelog-path'],
133
- releaseAs: this.resolveReleaseAs(pkgCfg['release-as'], config['release-as']),
134
- draft: (_f = pkgCfg['draft']) !== null && _f !== void 0 ? _f : config['draft'],
135
- skipGithubRelease: (_h = (_g = pkgCfg['skip-github-release']) !== null && _g !== void 0 ? _g : config['skip-github-release']) !== null && _h !== void 0 ? _h : false,
172
+ if (expectedVersion.toString() === tagName.version.toString()) {
173
+ logger_1.logger.debug(`Found release for path ${path}, ${release.tagName}`);
174
+ releaseShasByPath[path] = release.sha;
175
+ releasesByPath[path] = {
176
+ tag: tagName,
177
+ sha: release.sha,
178
+ notes: release.notes || '',
136
179
  };
137
- packages.push(pkg);
180
+ releasesFound += 1;
181
+ }
182
+ if (releasesFound >= expectedReleases) {
183
+ break;
138
184
  }
139
- this.configFile = { parsedPackages: packages, ...config };
140
- }
141
- return this.configFile;
142
- }
143
- // Default release-as only considered if non-empty string.
144
- // Per-pkg release-as may be:
145
- // 1. undefined: use default release-as if present, otherwise normal version
146
- // resolution (auto-increment from CC, fallback to defaultInitialVersion)
147
- // 1. non-empty string: use this version
148
- // 2. empty string: override default release-as if present, otherwise normal
149
- // version resolution.
150
- resolveReleaseAs(pkgRA, defaultRA) {
151
- let releaseAs;
152
- if (defaultRA) {
153
- releaseAs = defaultRA;
154
185
  }
155
- if (pkgRA !== undefined) {
156
- releaseAs = pkgRA;
186
+ const needsBootstrap = releasesFound < expectedReleases;
187
+ if (releasesFound < expectedReleases) {
188
+ logger_1.logger.warn(`Expected ${expectedReleases} releases, only found ${releasesFound}`);
189
+ }
190
+ for (const path in releasesByPath) {
191
+ const release = releasesByPath[path];
192
+ logger_1.logger.debug(`release for path: ${path}, version: ${release.tag.version.toString()}, sha: ${release.sha}`);
193
+ }
194
+ // iterate through commits and collect commits until we have
195
+ // seen all release commits
196
+ logger_1.logger.info('Collecting commits since all latest releases');
197
+ const commits = [];
198
+ const commitGenerator = this.github.mergeCommitIterator(this.targetBranch, 500);
199
+ const releaseShas = new Set(Object.values(releaseShasByPath));
200
+ logger_1.logger.debug(releaseShas);
201
+ const expectedShas = releaseShas.size;
202
+ // sha => release pull request
203
+ const releasePullRequestsBySha = {};
204
+ let releaseCommitsFound = 0;
205
+ for await (const commit of commitGenerator) {
206
+ if (releaseShas.has(commit.sha)) {
207
+ if (commit.pullRequest) {
208
+ releasePullRequestsBySha[commit.sha] = commit.pullRequest;
209
+ }
210
+ else {
211
+ logger_1.logger.warn(`Release SHA ${commit.sha} did not have an associated pull request`);
212
+ }
213
+ releaseCommitsFound += 1;
214
+ }
215
+ if (this.lastReleaseSha && this.lastReleaseSha === commit.sha) {
216
+ logger_1.logger.info(`Using configured lastReleaseSha ${this.lastReleaseSha} as last commit.`);
217
+ break;
218
+ }
219
+ else if (needsBootstrap && commit.sha === this.bootstrapSha) {
220
+ logger_1.logger.info(`Needed bootstrapping, found configured bootstrapSha ${this.bootstrapSha}`);
221
+ break;
222
+ }
223
+ else if (!needsBootstrap && releaseCommitsFound >= expectedShas) {
224
+ // found enough commits
225
+ break;
226
+ }
227
+ commits.push({
228
+ sha: commit.sha,
229
+ message: commit.message,
230
+ files: commit.files,
231
+ });
157
232
  }
158
- if (!releaseAs) {
159
- releaseAs = undefined;
233
+ if (releaseCommitsFound < expectedShas) {
234
+ logger_1.logger.warn(`Expected ${expectedShas} commits, only found ${releaseCommitsFound}`);
160
235
  }
161
- return releaseAs;
162
- }
163
- async getPackagesToRelease(allCommits, sha) {
164
- const packages = (await this.getConfigJson()).parsedPackages;
165
- const [manifestVersions, atSha] = await this.getManifestVersions(sha);
236
+ // split commits by path
237
+ logger_1.logger.info(`Splitting ${commits.length} commits by path`);
166
238
  const cs = new commit_split_1.CommitSplit({
167
239
  includeEmpty: true,
168
- packagePaths: packages.map(p => p.path),
240
+ packagePaths: Object.keys(this.repositoryConfig),
169
241
  });
170
- const commitsPerPath = cs.split(allCommits);
171
- const packagesToRelease = {};
172
- const missingVersionPaths = [];
173
- const defaultBranch = await this.gh.getDefaultBranch();
174
- for (const pkg of packages) {
175
- // The special path of '.' indicates the root module is being released
176
- // in this case, use the entire list of commits:
177
- const commits = pkg.path === '.' ? allCommits : commitsPerPath[pkg.path];
178
- if (!commits || commits.length === 0) {
242
+ const commitsPerPath = cs.split(commits);
243
+ let newReleasePullRequests = [];
244
+ for (const path in this.repositoryConfig) {
245
+ const config = this.repositoryConfig[path];
246
+ logger_1.logger.info(`Building candidate release pull request for path: ${path}`);
247
+ logger_1.logger.debug(`type: ${config.releaseType}`);
248
+ logger_1.logger.debug(`targetBranch: ${this.targetBranch}`);
249
+ const pathCommits = commitsAfterSha(path === exports.ROOT_PROJECT_PATH ? commits : commitsPerPath[path], releaseShasByPath[path]);
250
+ if (!pathCommits || pathCommits.length === 0) {
251
+ logger_1.logger.info(`No commits for path: ${path}, skipping`);
179
252
  continue;
180
253
  }
181
- const lastVersion = manifestVersions.get(pkg.path);
182
- if (!lastVersion) {
183
- this.checkpoint(`Failed to find version for ${pkg.path} in ` +
184
- `${this.manifestFileName} at ${atSha} of ${defaultBranch}`, checkpoint_1.CheckpointType.Failure);
185
- missingVersionPaths.push(pkg.path);
186
- }
187
- else {
188
- this.checkpoint(`Found version ${lastVersion} for ${pkg.path} in ` +
189
- `${this.manifestFileName} at ${atSha} of ${defaultBranch}`, checkpoint_1.CheckpointType.Success);
254
+ logger_1.logger.debug(`commits: ${pathCommits.length}`);
255
+ const latestReleasePullRequest = releasePullRequestsBySha[releaseShasByPath[path]];
256
+ if (!latestReleasePullRequest) {
257
+ logger_1.logger.warn('No latest release pull request found.');
190
258
  }
191
- packagesToRelease[pkg.path] = {
192
- commits,
193
- lastVersion,
194
- config: pkg,
195
- };
196
- }
197
- if (missingVersionPaths.length > 0) {
198
- const headManifestVersions = await this.getManifestVersions(false, missingVersionPaths);
199
- for (const missingVersionPath of missingVersionPaths) {
200
- const headVersion = headManifestVersions.get(missingVersionPath);
201
- if (headVersion === undefined) {
202
- this.checkpoint(`Failed to find version for ${missingVersionPath} in ` +
203
- `${this.manifestFileName} at tip of ${defaultBranch}`, checkpoint_1.CheckpointType.Failure);
259
+ const strategy = strategiesByPath[path];
260
+ const latestRelease = releasesByPath[path];
261
+ const releasePullRequest = await strategy.buildReleasePullRequest(pathCommits, latestRelease, config.draft, this.labels);
262
+ if (releasePullRequest) {
263
+ if (releasePullRequest.version) {
264
+ const versionsMap = new Map();
265
+ versionsMap.set(path, releasePullRequest.version);
266
+ releasePullRequest.updates.push({
267
+ path: this.manifestPath,
268
+ createIfMissing: false,
269
+ updater: new release_please_manifest_1.ReleasePleaseManifest({
270
+ version: releasePullRequest.version,
271
+ versionsMap,
272
+ }),
273
+ });
204
274
  }
205
- packagesToRelease[missingVersionPath].lastVersion = headVersion;
275
+ newReleasePullRequests.push({
276
+ path,
277
+ config,
278
+ pullRequest: releasePullRequest,
279
+ });
206
280
  }
207
281
  }
208
- return Object.values(packagesToRelease);
282
+ // Build plugins
283
+ const plugins = this.plugins.map(pluginType => factory_1.buildPlugin({
284
+ type: pluginType,
285
+ github: this.github,
286
+ targetBranch: this.targetBranch,
287
+ repositoryConfig: this.repositoryConfig,
288
+ }));
289
+ // Combine pull requests into 1 unless configured for separate
290
+ // pull requests
291
+ if (!this.separatePullRequests) {
292
+ plugins.push(new merge_1.Merge(this.github, this.targetBranch, this.repositoryConfig));
293
+ }
294
+ for (const plugin of plugins) {
295
+ newReleasePullRequests = await plugin.run(newReleasePullRequests);
296
+ }
297
+ return newReleasePullRequests.map(pullRequestWithConfig => pullRequestWithConfig.pullRequest);
209
298
  }
210
- async validateJsonFile(getFileMethod, fileName) {
211
- let response = {
212
- valid: false,
213
- obj: undefined,
214
- };
215
- try {
216
- const obj = await this[getFileMethod]();
217
- if (obj.constructor.name === 'Object') {
218
- response = { valid: true, obj: obj };
299
+ /**
300
+ * Opens/updates all candidate release pull requests for this repository.
301
+ *
302
+ * @returns {number[]} Pull request numbers of release pull requests
303
+ */
304
+ async createPullRequests() {
305
+ const candidatePullRequests = await this.buildPullRequests();
306
+ if (candidatePullRequests.length === 0) {
307
+ return [];
308
+ }
309
+ // if there are any merged, pending release pull requests, don't open
310
+ // any new release PRs
311
+ const mergedPullRequestsGenerator = this.findMergedReleasePullRequests();
312
+ for await (const _ of mergedPullRequestsGenerator) {
313
+ logger_1.logger.warn('There are untagged, merged release PRs outstanding - aborting');
314
+ return [];
315
+ }
316
+ // collect open release pull requests
317
+ logger_1.logger.info('Looking for open release pull requests');
318
+ const openPullRequests = [];
319
+ const generator = this.github.pullRequestIterator(this.targetBranch, 'OPEN');
320
+ for await (const openPullRequest of generator) {
321
+ if (hasAllLabels(this.labels, openPullRequest.labels) &&
322
+ branch_name_1.BranchName.parse(openPullRequest.headBranchName) &&
323
+ pull_request_body_1.PullRequestBody.parse(openPullRequest.body)) {
324
+ openPullRequests.push(openPullRequest);
219
325
  }
220
326
  }
221
- catch (e) {
222
- let errMsg;
223
- if (e instanceof SyntaxError) {
224
- errMsg = `Invalid JSON in ${fileName}`;
225
- }
226
- else {
227
- errMsg = `Unable to ${getFileMethod}(${fileName}): ${e.message}`;
228
- }
229
- this.checkpoint(errMsg, checkpoint_1.CheckpointType.Failure);
327
+ logger_1.logger.info(`found ${openPullRequests.length} open release pull requests.`);
328
+ const promises = [];
329
+ for (const pullRequest of candidatePullRequests) {
330
+ promises.push(this.createOrUpdatePullRequest(pullRequest, openPullRequests));
230
331
  }
231
- return response;
332
+ return await Promise.all(promises);
232
333
  }
233
- async validate() {
234
- var _a;
235
- const configValidation = await this.validateJsonFile('getConfigJson', this.configFileName);
236
- let validConfig = false;
237
- if (configValidation.valid) {
238
- const obj = configValidation.obj;
239
- validConfig = !!Object.keys((_a = obj.packages) !== null && _a !== void 0 ? _a : {}).length;
240
- if (!validConfig) {
241
- this.checkpoint(`No packages found: ${this.configFileName}`, checkpoint_1.CheckpointType.Failure);
334
+ async createOrUpdatePullRequest(pullRequest, openPullRequests) {
335
+ // look for existing, open pull rquest
336
+ const existing = openPullRequests.find(openPullRequest => openPullRequest.headBranchName === pullRequest.headRefName);
337
+ if (existing) {
338
+ // If unchanged, no need to push updates
339
+ if (existing.body === pullRequest.body.toString()) {
340
+ logger_1.logger.info(`PR https://github.com/${this.repository.owner}/${this.repository.repo}/pull/${existing.number} remained the same`);
341
+ return undefined;
242
342
  }
343
+ const updatedPullRequest = await this.github.updatePullRequest(existing.number, pullRequest, this.targetBranch, {
344
+ fork: this.fork,
345
+ signoffUser: this.signoffUser,
346
+ });
347
+ return updatedPullRequest.number;
243
348
  }
244
- const manifestValidation = await this.validateJsonFile('getManifestJson', this.manifestFileName);
245
- let validManifest = false;
246
- if (manifestValidation.valid) {
247
- validManifest = true;
248
- const versions = new Map(Object.entries(manifestValidation.obj));
249
- for (const [_, version] of versions) {
250
- if (typeof version !== 'string') {
251
- validManifest = false;
252
- this.checkpoint(`${this.manifestFileName} must only contain string values`, checkpoint_1.CheckpointType.Failure);
253
- break;
254
- }
255
- }
349
+ else {
350
+ const newPullRequest = await this.github.createReleasePullRequest(pullRequest, this.targetBranch, {
351
+ fork: this.fork,
352
+ signoffUser: this.signoffUser,
353
+ });
354
+ return newPullRequest.number;
256
355
  }
257
- return validConfig && validManifest;
258
356
  }
259
- async getReleasePR(pkg) {
260
- const { releaseType, draft, ...options } = pkg;
261
- const releaserOptions = {
262
- monorepoTags: true,
263
- ...options,
264
- };
265
- const releaserClass = _1.factory.releasePRClass(releaseType);
266
- const releasePR = new releaserClass({
267
- github: this.gh,
268
- skipDependencyUpdates: true,
269
- ...releaserOptions,
270
- });
271
- return [releasePR, draft];
272
- }
273
- async runReleasers(packagesForReleasers, sha) {
274
- const newManifestVersions = new Map();
275
- const pkgsWithChanges = [];
276
- for (const pkg of packagesForReleasers) {
277
- const [releasePR] = await this.getReleasePR(pkg.config);
278
- const pkgName = await releasePR.getPackageName();
279
- const displayTag = `${releasePR.constructor.name}(${pkgName.name})`;
280
- this.checkpoint(`Processing package: ${displayTag}`, checkpoint_1.CheckpointType.Success);
281
- if (pkg.lastVersion === undefined) {
282
- this.checkpoint(`Falling back to default version for ${displayTag}: ` +
283
- releasePR.defaultInitialVersion(), checkpoint_1.CheckpointType.Failure);
357
+ async *findMergedReleasePullRequests() {
358
+ // Find merged release pull requests
359
+ const pullRequestGenerator = this.github.pullRequestIterator(this.targetBranch, 'MERGED', 200);
360
+ for await (const pullRequest of pullRequestGenerator) {
361
+ if (!hasAllLabels(this.labels, pullRequest.labels)) {
362
+ continue;
284
363
  }
285
- const openPROptions = await releasePR.getOpenPROptions(pkg.commits, pkg.lastVersion
286
- ? {
287
- name: pkgName.getComponent() +
288
- releasePR.tagSeparator() +
289
- 'v' +
290
- pkg.lastVersion,
291
- sha: sha !== null && sha !== void 0 ? sha : 'beginning of time',
292
- version: pkg.lastVersion,
293
- }
294
- : undefined);
295
- if (openPROptions) {
296
- pkg.config.packageName = (await releasePR.getPackageName()).name;
297
- const changes = await this.gh.getChangeSet(openPROptions.updates, await this.gh.getDefaultBranch());
298
- pkgsWithChanges.push({
299
- config: pkg.config,
300
- prData: { version: openPROptions.version, changes },
301
- });
302
- newManifestVersions.set(pkg.config.path, openPROptions.version);
364
+ logger_1.logger.debug(`Found pull request #${pullRequest.number}: '${pullRequest.title}'`);
365
+ const pullRequestBody = pull_request_body_1.PullRequestBody.parse(pullRequest.body);
366
+ if (!pullRequestBody) {
367
+ logger_1.logger.debug('could not parse pull request body as a release PR');
368
+ continue;
303
369
  }
370
+ yield pullRequest;
304
371
  }
305
- return [newManifestVersions, pkgsWithChanges];
306
372
  }
307
- async getManifestChanges(newManifestVersions) {
308
- // TODO: simplify `Update.contents?` to just be a string - no need to
309
- // roundtrip through a GitHubFileContents
310
- const manifestContents = {
311
- sha: '',
312
- parsedContent: '',
313
- content: Buffer.from(JSON.stringify(await this.getManifestJson())).toString('base64'),
314
- };
315
- const manifestUpdate = new release_please_manifest_1.ReleasePleaseManifest({
316
- changelogEntry: '',
317
- packageName: '',
318
- path: this.manifestFileName,
319
- version: '',
320
- versions: newManifestVersions,
321
- contents: manifestContents,
322
- });
323
- return await this.gh.getChangeSet([manifestUpdate], await this.gh.getDefaultBranch());
324
- }
325
- buildPRBody(pkg) {
326
- var _a, _b;
327
- const version = pkg.prData.version;
328
- let body = '<details><summary>' +
329
- `${pkg.config.packageName}: ${version}` +
330
- '</summary>';
331
- let changelogPath = (_a = pkg.config.changelogPath) !== null && _a !== void 0 ? _a : 'CHANGELOG.md';
332
- if (pkg.config.path !== '.') {
333
- changelogPath = `${pkg.config.path}/${changelogPath}`;
334
- }
335
- const changelog = (_b = pkg.prData.changes.get(changelogPath)) === null || _b === void 0 ? void 0 : _b.content;
336
- if (!changelog) {
337
- this.checkpoint(`Failed to find ${changelogPath}`, checkpoint_1.CheckpointType.Failure);
373
+ /**
374
+ * Find merged, untagged releases and build candidate releases to tag.
375
+ *
376
+ * @returns {CandidateRelease[]} List of release candidates
377
+ */
378
+ async buildReleases() {
379
+ logger_1.logger.info('Building releases');
380
+ const strategiesByPath = await this.getStrategiesByPath();
381
+ // Find merged release pull requests
382
+ const generator = await this.findMergedReleasePullRequests();
383
+ const releases = [];
384
+ for await (const pullRequest of generator) {
385
+ logger_1.logger.info('Looking at files touched by path');
386
+ const cs = new commit_split_1.CommitSplit({
387
+ includeEmpty: true,
388
+ packagePaths: Object.keys(this.repositoryConfig),
389
+ });
390
+ const commits = [
391
+ {
392
+ sha: pullRequest.sha,
393
+ message: pullRequest.title,
394
+ files: pullRequest.files,
395
+ },
396
+ ];
397
+ const commitsPerPath = cs.split(commits);
398
+ for (const path in this.repositoryConfig) {
399
+ const config = this.repositoryConfig[path];
400
+ logger_1.logger.info(`Building release for path: ${path}`);
401
+ logger_1.logger.debug(`type: ${config.releaseType}`);
402
+ logger_1.logger.debug(`targetBranch: ${this.targetBranch}`);
403
+ const pathCommits = path === exports.ROOT_PROJECT_PATH ? commits : commitsPerPath[path];
404
+ if (!pathCommits || pathCommits.length === 0) {
405
+ logger_1.logger.info(`No commits for path: ${path}, skipping`);
406
+ continue;
407
+ }
408
+ const strategy = strategiesByPath[path];
409
+ const release = await strategy.buildRelease(pullRequest);
410
+ if (release) {
411
+ releases.push({
412
+ ...release,
413
+ pullRequest,
414
+ });
415
+ }
416
+ }
338
417
  }
339
- else {
340
- const match = changelog.match(
341
- // changelog entries start like
342
- // ## 1.0.0 (1983...
343
- // ## [4.0.0](https...
344
- // ### [1.2.4](https...
345
- RegExp(`.*###? \\[?${version}\\]?.*?\n(?<currentEntry>.*?)` +
346
- // either the next changelog or new lines / spaces to the end if
347
- // this is the first entry in the changelog
348
- '(\n###? [0-9[].*|[\n ]*$)', 's'));
349
- if (!match) {
350
- this.checkpoint(`Failed to find entry in changelog for ${version}`, checkpoint_1.CheckpointType.Failure);
418
+ return releases;
419
+ }
420
+ /**
421
+ * Find merged, untagged releases. For each release, create a GitHub release,
422
+ * comment on the pull request used to generated it and update the pull request
423
+ * labels.
424
+ *
425
+ * @returns {GitHubRelease[]} List of created GitHub releases
426
+ */
427
+ async createReleases() {
428
+ const releasesByPullRequest = {};
429
+ const pullRequestsByNumber = {};
430
+ for (const release of await this.buildReleases()) {
431
+ pullRequestsByNumber[release.pullRequest.number] = release.pullRequest;
432
+ if (releasesByPullRequest[release.pullRequest.number]) {
433
+ releasesByPullRequest[release.pullRequest.number].push(release);
351
434
  }
352
435
  else {
353
- const { currentEntry } = match.groups;
354
- body += '\n\n\n' + currentEntry.trim() + '\n';
436
+ releasesByPullRequest[release.pullRequest.number] = [release];
355
437
  }
356
438
  }
357
- body += '</details>\n';
358
- return body;
359
- }
360
- async buildManifestPR(newManifestVersions,
361
- // using version, changes
362
- packages) {
363
- let body = ':robot: I have created a release \\*beep\\* \\*boop\\*\n---\n';
364
- let changes = new Map();
365
- for (const pkg of packages) {
366
- body += this.buildPRBody(pkg);
367
- changes = new Map([...changes, ...pkg.prData.changes]);
439
+ const promises = [];
440
+ for (const pullNumber in releasesByPullRequest) {
441
+ promises.push(this.createReleasesForPullRequest(releasesByPullRequest[pullNumber], pullRequestsByNumber[pullNumber]));
368
442
  }
369
- const manifestChanges = await this.getManifestChanges(newManifestVersions);
370
- changes = new Map([...changes, ...manifestChanges]);
371
- body +=
372
- '\n\nThis PR was generated with [Release Please]' +
373
- `(https://github.com/googleapis/${constants_1.RELEASE_PLEASE}). See [documentation]` +
374
- `(https://github.com/googleapis/${constants_1.RELEASE_PLEASE}#${constants_1.RELEASE_PLEASE}).`;
375
- return [body, changes];
443
+ const releases = await Promise.all(promises);
444
+ return releases.reduce((collection, r) => collection.concat(r), []);
376
445
  }
377
- async getPlugins() {
378
- var _a;
379
- const plugins = [];
380
- const config = await this.getConfigJson();
381
- for (const p of (_a = config.plugins) !== null && _a !== void 0 ? _a : []) {
382
- plugins.push(plugins_1.getPlugin(p, this.gh, config));
383
- }
384
- return plugins;
446
+ async createReleasesForPullRequest(releases, pullRequest) {
447
+ // create the release
448
+ const promises = [];
449
+ for (const release of releases) {
450
+ promises.push(this.createRelease(release));
451
+ }
452
+ const githubReleases = await Promise.all(promises);
453
+ // adjust tags on pullRequest
454
+ await Promise.all([
455
+ this.github.removeIssueLabels(this.labels, pullRequest.number),
456
+ this.github.addIssueLabels(this.releaseLabels, pullRequest.number),
457
+ ]);
458
+ return githubReleases;
385
459
  }
386
- async resolveLastReleaseSha(branchName) {
387
- const config = await this.getConfigJson();
388
- let lastReleaseSha;
389
- let source = 'no last release sha found';
390
- if (config['last-release-sha']) {
391
- lastReleaseSha = config['last-release-sha'];
392
- source = 'last-release-sha';
393
- }
394
- else {
395
- const lastMergedPR = await this.gh.lastMergedPRByHeadBranch(branchName);
396
- if (lastMergedPR) {
397
- lastReleaseSha = lastMergedPR.sha;
398
- source = 'last-release-pr';
399
- }
400
- else if (config['bootstrap-sha']) {
401
- lastReleaseSha = config['bootstrap-sha'];
402
- source = 'bootstrap-sha';
403
- }
404
- }
405
- this.checkpoint(`Found last release sha "${lastReleaseSha}" using "${source}"`, checkpoint_1.CheckpointType.Success);
406
- return lastReleaseSha;
460
+ async createRelease(release) {
461
+ const githubRelease = await this.github.createRelease(release);
462
+ // comment on pull request
463
+ const comment = `:robot: Release is at ${githubRelease.url} :sunflower:`;
464
+ await this.github.commentOnIssue(comment, release.pullRequest.number);
465
+ return githubRelease;
407
466
  }
408
- async pullRequest() {
409
- const valid = await this.validate();
410
- if (!valid) {
411
- return;
412
- }
413
- const branchName = (await this.getBranchName()).toString();
414
- const lastReleaseSha = await this.resolveLastReleaseSha(branchName);
415
- const commits = await this.gh.commitsSinceShaRest(lastReleaseSha);
416
- const packagesForReleasers = await this.getPackagesToRelease(commits, lastReleaseSha);
417
- let [newManifestVersions, pkgsWithChanges] = await this.runReleasers(packagesForReleasers, lastReleaseSha);
418
- if (pkgsWithChanges.length === 0) {
419
- this.checkpoint('No user facing changes to release', checkpoint_1.CheckpointType.Success);
420
- return;
421
- }
422
- for (const plugin of await this.getPlugins()) {
423
- [newManifestVersions, pkgsWithChanges] = await plugin.run(newManifestVersions, pkgsWithChanges);
424
- }
425
- const [body, changes] = await this.buildManifestPR(newManifestVersions, pkgsWithChanges);
426
- const title = `chore: release ${await this.gh.getDefaultBranch()}`;
427
- // Sign-off message if signoff option is enabled
428
- const message = this.signoff
429
- ? signoff_commit_message_1.signoffCommitMessage(title, this.signoff)
430
- : title;
431
- const pr = await this.gh.openPR({
432
- branch: branchName,
433
- title,
434
- message,
435
- body: body,
436
- updates: [],
437
- labels: constants_1.DEFAULT_LABELS,
438
- changes,
439
- });
440
- if (pr) {
441
- await this.gh.addLabels(constants_1.DEFAULT_LABELS, pr);
467
+ async getStrategiesByPath() {
468
+ if (!this._strategiesByPath) {
469
+ logger_1.logger.info('Building strategies by path');
470
+ this._strategiesByPath = {};
471
+ for (const path in this.repositoryConfig) {
472
+ const config = this.repositoryConfig[path];
473
+ logger_1.logger.debug(`${path}: ${config.releaseType}`);
474
+ const strategy = await factory_1.buildStrategy({
475
+ ...config,
476
+ github: this.github,
477
+ path,
478
+ targetBranch: this.targetBranch,
479
+ });
480
+ this._strategiesByPath[path] = strategy;
481
+ }
442
482
  }
443
- return pr;
483
+ return this._strategiesByPath;
444
484
  }
445
- async githubRelease() {
446
- var _a;
447
- const valid = await this.validate();
448
- if (!valid) {
449
- return;
450
- }
451
- const branchName = (await this.getBranchName()).toString();
452
- const lastMergedPR = await this.gh.lastMergedPRByHeadBranch(branchName);
453
- if (lastMergedPR === undefined) {
454
- this.checkpoint('Unable to find last merged Manifest PR for tagging', checkpoint_1.CheckpointType.Failure);
455
- return;
456
- }
457
- if (lastMergedPR.labels.includes(github_release_1.GITHUB_RELEASE_LABEL)) {
458
- this.checkpoint('Releases already created for last merged release PR', checkpoint_1.CheckpointType.Success);
459
- return;
460
- }
461
- if (!lastMergedPR.labels.includes(constants_1.DEFAULT_LABELS[0])) {
462
- this.checkpoint(`Warning: last merged PR(#${lastMergedPR.number}) is missing ` +
463
- `label "${constants_1.DEFAULT_LABELS[0]}" but has not yet been ` +
464
- `labeled "${github_release_1.GITHUB_RELEASE_LABEL}". If PR(#${lastMergedPR.number}) ` +
465
- 'is meant to be a release PR, please apply the ' +
466
- `label "${constants_1.DEFAULT_LABELS[0]}".`, checkpoint_1.CheckpointType.Failure);
467
- return;
468
- }
469
- const packagesForReleasers = await this.getPackagesToRelease(
470
- // use the lastMergedPR.sha as a Commit: lastMergedPR.files will inform
471
- // getPackagesToRelease() what packages had changes (i.e. at least one
472
- // file under their path changed in the lastMergedPR such as
473
- // "packages/mypkg/package.json"). These are exactly the packages we want
474
- // to create releases/tags for.
475
- [{ sha: lastMergedPR.sha, message: '', files: lastMergedPR.files }], lastMergedPR.sha);
476
- const releases = {};
477
- let allReleasesCreated = !!packagesForReleasers.length;
478
- for (const pkg of packagesForReleasers) {
479
- const [releasePR, draft] = await this.getReleasePR(pkg.config);
480
- const pkgName = (await releasePR.getPackageName()).name;
481
- const pkgLogDisp = `${releasePR.constructor.name}(${pkgName})`;
482
- if (!pkg.lastVersion) {
483
- // a user manually modified the manifest file on the release branch
484
- // right before merging it and deleted the entry for this pkg.
485
- this.checkpoint(`Unable to find last version for ${pkgLogDisp}.`, checkpoint_1.CheckpointType.Failure);
486
- releases[pkg.config.path] = undefined;
487
- continue;
488
- }
489
- if (pkg.config.skipGithubRelease) {
490
- this.gh.commentOnIssue(`:robot: ${pkgName} not configured for release :no_entry_sign:`, lastMergedPR.number);
491
- releases[pkg.config.path] = undefined;
492
- continue;
493
- }
494
- this.checkpoint('Creating release for ' + `${pkgLogDisp}@${pkg.lastVersion}`, checkpoint_1.CheckpointType.Success);
495
- const releaser = new github_release_1.GitHubRelease({
496
- github: this.gh,
497
- releasePR,
498
- draft,
499
- });
500
- let release;
501
- try {
502
- release = await releaser.createRelease(pkg.lastVersion, lastMergedPR);
503
- }
504
- catch (err) {
505
- // There is no transactional bulk create releases API. Previous runs
506
- // may have failed due to transient infrastructure problems part way
507
- // through creating releases. Here we skip any releases that were
508
- // already successfully created.
509
- //
510
- // Note about `draft` releases: The GitHub API Release unique key is
511
- // `tag_name`. However, if `draft` is true, no git tag is created. Thus
512
- // multiple `draft` releases can be created with the exact same inputs.
513
- // (It's a tad confusing because `tag_name` still comes back populated
514
- // in these calls but the tag doesn't actually exist).
515
- // A draft release can even be created with a `tag_name` referring to an
516
- // existing tag referenced by another release.
517
- // However, GitHub will prevent "publishing" any draft release that
518
- // would cause a duplicate tag to be created. release-please manifest
519
- // users specifying the "release-draft" option could run into this
520
- // duplicate releases scenario. It's easy enough to just delete the
521
- // duplicate draft entries in the UI (or API).
522
- if (err.status === 422 && ((_a = err.errors) === null || _a === void 0 ? void 0 : _a.length)) {
523
- if (err.errors[0].code === 'already_exists' &&
524
- err.errors[0].field === 'tag_name') {
525
- this.checkpoint(`Release for ${pkgLogDisp}@${pkg.lastVersion} already exists`, checkpoint_1.CheckpointType.Success);
526
- }
527
- }
528
- else {
529
- // PR will not be tagged with GITHUB_RELEASE_LABEL so another run
530
- // can try again.
531
- allReleasesCreated = false;
532
- await this.gh.commentOnIssue(`:robot: Failed to create release for ${pkgName} :cloud:`, lastMergedPR.number);
533
- this.checkpoint('Failed to create release for ' +
534
- `${pkgLogDisp}@${pkg.lastVersion}: ${err.message}`, checkpoint_1.CheckpointType.Failure);
485
+ async getPathsByComponent() {
486
+ if (!this._pathsByComponent) {
487
+ this._pathsByComponent = {};
488
+ const strategiesByPath = await this.getStrategiesByPath();
489
+ for (const path in this.repositoryConfig) {
490
+ const strategy = strategiesByPath[path];
491
+ const component = strategy.component || (await strategy.getDefaultComponent()) || '';
492
+ if (this._pathsByComponent[component]) {
493
+ logger_1.logger.warn(`Multiple paths for ${component}: ${this._pathsByComponent[component]}, ${path}`);
535
494
  }
536
- releases[pkg.config.path] = undefined;
537
- continue;
538
- }
539
- if (release) {
540
- await this.gh.commentOnIssue(`:robot: Release for ${pkgName} is at ${release.html_url} :sunflower:`, lastMergedPR.number);
541
- releases[pkg.config.path] = releaser.releaseResponse({
542
- release,
543
- version: pkg.lastVersion,
544
- sha: lastMergedPR.sha,
545
- number: lastMergedPR.number,
546
- });
495
+ this._pathsByComponent[component] = path;
496
+ logger_1.logger.info(this._pathsByComponent);
547
497
  }
548
498
  }
549
- if (allReleasesCreated) {
550
- await this.gh.addLabels([github_release_1.GITHUB_RELEASE_LABEL], lastMergedPR.number);
551
- await this.gh.removeLabels(constants_1.DEFAULT_LABELS, lastMergedPR.number);
552
- }
553
- return releases;
499
+ return this._pathsByComponent;
554
500
  }
555
501
  }
556
502
  exports.Manifest = Manifest;
503
+ /**
504
+ * Helper to convert parsed JSON releaser config into ReleaserConfig for
505
+ * the Manifest.
506
+ *
507
+ * @param {ReleaserPackageConfig} config Parsed configuration from JSON file.
508
+ * @returns {ReleaserConfig}
509
+ */
510
+ function extractReleaserConfig(config) {
511
+ return {
512
+ releaseType: config['release-type'] || 'node',
513
+ bumpMinorPreMajor: config['bump-minor-pre-major'],
514
+ bumpPatchForMinorPreMajor: config['bump-patch-for-minor-pre-major'],
515
+ changelogSections: config['changelog-sections'],
516
+ changelogPath: config['changelog-path'],
517
+ releaseAs: config['release-as'],
518
+ skipGithubRelease: config['skip-github-release'],
519
+ draft: config.draft,
520
+ component: config['component'],
521
+ packageName: config['package-name'],
522
+ versionFile: config['version-file'],
523
+ extraFiles: config['extra-files'],
524
+ };
525
+ }
526
+ /**
527
+ * Helper to convert fetch the manifest config from the repository and
528
+ * parse into configuration for the Manifest.
529
+ *
530
+ * @param {GitHub} github GitHub client
531
+ * @param {string} configFile Path in the repository to the manifest config
532
+ * @param {string} branch Branch to fetch the config file from
533
+ */
534
+ async function parseConfig(github, configFile, branch) {
535
+ const config = await github.getFileJson(configFile, branch);
536
+ const defaultConfig = extractReleaserConfig(config);
537
+ const repositoryConfig = {};
538
+ for (const path in config.packages) {
539
+ repositoryConfig[path] = mergeReleaserConfig(defaultConfig, extractReleaserConfig(config.packages[path]));
540
+ }
541
+ const manifestOptions = {
542
+ bootstrapSha: config['bootstrap-sha'],
543
+ lastReleaseSha: config['last-release-sha'],
544
+ alwaysLinkLocal: config['always-link-local'],
545
+ separatePullRequests: config['separate-pull-requests'],
546
+ plugins: config['plugins'],
547
+ };
548
+ return { config: repositoryConfig, options: manifestOptions };
549
+ }
550
+ /**
551
+ * Helper to parse the manifest versions file.
552
+ *
553
+ * @param {GitHub} github GitHub client
554
+ * @param {string} manifestFile Path in the repository to the versions file
555
+ * @param {string} branch Branch to fetch the versions file from
556
+ */
557
+ async function parseReleasedVersions(github, manifestFile, branch) {
558
+ const manifestJson = await github.getFileJson(manifestFile, branch);
559
+ const releasedVersions = {};
560
+ for (const path in manifestJson) {
561
+ releasedVersions[path] = version_1.Version.parse(manifestJson[path]);
562
+ }
563
+ return releasedVersions;
564
+ }
565
+ /**
566
+ * Find the most recent matching release tag on the branch we're
567
+ * configured for.
568
+ *
569
+ * @param {string} prefix - Limit the release to a specific component.
570
+ * @param {boolean} preRelease - Whether or not to return pre-release
571
+ * versions. Defaults to false.
572
+ */
573
+ async function latestReleaseVersion(github, targetBranch, prefix) {
574
+ var _a;
575
+ const branchPrefix = prefix
576
+ ? prefix.endsWith('-')
577
+ ? prefix.replace(/-$/, '')
578
+ : prefix
579
+ : undefined;
580
+ logger_1.logger.info(`Looking for latest release on branch: ${targetBranch} with prefix: ${prefix}`);
581
+ // only look at the last 250 or so commits to find the latest tag - we
582
+ // don't want to scan the entire repository history if this repo has never
583
+ // been released
584
+ const generator = github.mergeCommitIterator(targetBranch, 250);
585
+ for await (const commitWithPullRequest of generator) {
586
+ const mergedPullRequest = commitWithPullRequest.pullRequest;
587
+ if (!mergedPullRequest) {
588
+ continue;
589
+ }
590
+ const branchName = branch_name_1.BranchName.parse(mergedPullRequest.headBranchName);
591
+ if (!branchName) {
592
+ continue;
593
+ }
594
+ // If branchPrefix is specified, ensure it is found in the branch name.
595
+ // If branchPrefix is not specified, component should also be undefined.
596
+ if (branchName.getComponent() !== branchPrefix) {
597
+ continue;
598
+ }
599
+ const pullRequestTitle = pull_request_title_1.PullRequestTitle.parse(mergedPullRequest.title);
600
+ if (!pullRequestTitle) {
601
+ continue;
602
+ }
603
+ const version = pullRequestTitle.getVersion();
604
+ if ((_a = version === null || version === void 0 ? void 0 : version.preRelease) === null || _a === void 0 ? void 0 : _a.includes('SNAPSHOT')) {
605
+ // FIXME, don't hardcode this
606
+ continue;
607
+ }
608
+ return version;
609
+ }
610
+ return;
611
+ }
612
+ function mergeReleaserConfig(defaultConfig, pathConfig) {
613
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
614
+ return {
615
+ releaseType: (_a = pathConfig.releaseType) !== null && _a !== void 0 ? _a : defaultConfig.releaseType,
616
+ bumpMinorPreMajor: (_b = pathConfig.bumpMinorPreMajor) !== null && _b !== void 0 ? _b : defaultConfig.bumpMinorPreMajor,
617
+ bumpPatchForMinorPreMajor: (_c = pathConfig.bumpPatchForMinorPreMajor) !== null && _c !== void 0 ? _c : defaultConfig.bumpPatchForMinorPreMajor,
618
+ changelogSections: (_d = pathConfig.changelogSections) !== null && _d !== void 0 ? _d : defaultConfig.changelogSections,
619
+ changelogPath: (_e = pathConfig.changelogPath) !== null && _e !== void 0 ? _e : defaultConfig.changelogPath,
620
+ releaseAs: (_f = pathConfig.releaseAs) !== null && _f !== void 0 ? _f : defaultConfig.releaseAs,
621
+ skipGithubRelease: (_g = pathConfig.skipGithubRelease) !== null && _g !== void 0 ? _g : defaultConfig.skipGithubRelease,
622
+ draft: (_h = pathConfig.draft) !== null && _h !== void 0 ? _h : defaultConfig.draft,
623
+ component: (_j = pathConfig.component) !== null && _j !== void 0 ? _j : defaultConfig.component,
624
+ packageName: (_k = pathConfig.packageName) !== null && _k !== void 0 ? _k : defaultConfig.packageName,
625
+ versionFile: (_l = pathConfig.versionFile) !== null && _l !== void 0 ? _l : defaultConfig.versionFile,
626
+ extraFiles: (_m = pathConfig.extraFiles) !== null && _m !== void 0 ? _m : defaultConfig.extraFiles,
627
+ };
628
+ }
629
+ /**
630
+ * Helper to compare if a list of labels fully contains another list of labels
631
+ * @param {string[]} expected List of labels expected to be contained
632
+ * @param {string[]} existing List of existing labels to consider
633
+ */
634
+ function hasAllLabels(expected, existing) {
635
+ const existingSet = new Set(existing);
636
+ for (const label of expected) {
637
+ if (!existingSet.has(label)) {
638
+ return false;
639
+ }
640
+ }
641
+ return true;
642
+ }
643
+ function commitsAfterSha(commits, lastReleaseSha) {
644
+ if (!commits) {
645
+ return [];
646
+ }
647
+ const index = commits.findIndex(commit => commit.sha === lastReleaseSha);
648
+ if (index === -1) {
649
+ return commits;
650
+ }
651
+ return commits.slice(0, index);
652
+ }
557
653
  //# sourceMappingURL=manifest.js.map