Repository: intuit/auto
Pull request #1424 (2020-07-30 18:24:33)

Fix various rate limiting issues
# What Changed

previously we would just error out on the abuse limit, now we will retry

# Why

#1422 (I'm not sure this PR solves for #1422)

<!-- GITHUB_RELEASE PR BODY: canary-version -->
<details>
  <summary>📦 Published PR as canary version: <code>under canary scope @auto-canary@9.49.3-canary.1424.17806.0</code></summary>
  <br />
  
  :sparkles: Test out this PR locally via:
  
  ```bash
  npm install @auto-canary/bot-list@9.49.3-canary.1424.17806.0
  npm install @auto-canary/auto@9.49.3-canary.1424.17806.0
  npm install @auto-canary/core@9.49.3-canary.1424.17806.0
  npm install @auto-canary/all-contributors@9.49.3-canary.1424.17806.0
  npm install @auto-canary/brew@9.49.3-canary.1424.17806.0
  npm install @auto-canary/chrome@9.49.3-canary.1424.17806.0
  npm install @auto-canary/cocoapods@9.49.3-canary.1424.17806.0
  npm install @auto-canary/conventional-commits@9.49.3-canary.1424.17806.0
  npm install @auto-canary/crates@9.49.3-canary.1424.17806.0
  npm install @auto-canary/exec@9.49.3-canary.1424.17806.0
  npm install @auto-canary/first-time-contributor@9.49.3-canary.1424.17806.0
  npm install @auto-canary/gem@9.49.3-canary.1424.17806.0
  npm install @auto-canary/gh-pages@9.49.3-canary.1424.17806.0
  npm install @auto-canary/git-tag@9.49.3-canary.1424.17806.0
  npm install @auto-canary/gradle@9.49.3-canary.1424.17806.0
  npm install @auto-canary/jira@9.49.3-canary.1424.17806.0
  npm install @auto-canary/maven@9.49.3-canary.1424.17806.0
  npm install @auto-canary/npm@9.49.3-canary.1424.17806.0
  npm install @auto-canary/omit-commits@9.49.3-canary.1424.17806.0
  npm install @auto-canary/omit-release-notes@9.49.3-canary.1424.17806.0
  npm install @auto-canary/released@9.49.3-canary.1424.17806.0
  npm install @auto-canary/s3@9.49.3-canary.1424.17806.0
  npm install @auto-canary/slack@9.49.3-canary.1424.17806.0
  npm install @auto-canary/twitter@9.49.3-canary.1424.17806.0
  npm install @auto-canary/upload-assets@9.49.3-canary.1424.17806.0
  # or 
  yarn add @auto-canary/bot-list@9.49.3-canary.1424.17806.0
  yarn add @auto-canary/auto@9.49.3-canary.1424.17806.0
  yarn add @auto-canary/core@9.49.3-canary.1424.17806.0
  yarn add @auto-canary/all-contributors@9.49.3-canary.1424.17806.0
  yarn add @auto-canary/brew@9.49.3-canary.1424.17806.0
  yarn add @auto-canary/chrome@9.49.3-canary.1424.17806.0
  yarn add @auto-canary/cocoapods@9.49.3-canary.1424.17806.0
  yarn add @auto-canary/conventional-commits@9.49.3-canary.1424.17806.0
  yarn add @auto-canary/crates@9.49.3-canary.1424.17806.0
  yarn add @auto-canary/exec@9.49.3-canary.1424.17806.0
  yarn add @auto-canary/first-time-contributor@9.49.3-canary.1424.17806.0
  yarn add @auto-canary/gem@9.49.3-canary.1424.17806.0
  yarn add @auto-canary/gh-pages@9.49.3-canary.1424.17806.0
  yarn add @auto-canary/git-tag@9.49.3-canary.1424.17806.0
  yarn add @auto-canary/gradle@9.49.3-canary.1424.17806.0
  yarn add @auto-canary/jira@9.49.3-canary.1424.17806.0
  yarn add @auto-canary/maven@9.49.3-canary.1424.17806.0
  yarn add @auto-canary/npm@9.49.3-canary.1424.17806.0
  yarn add @auto-canary/omit-commits@9.49.3-canary.1424.17806.0
  yarn add @auto-canary/omit-release-notes@9.49.3-canary.1424.17806.0
  yarn add @auto-canary/released@9.49.3-canary.1424.17806.0
  yarn add @auto-canary/s3@9.49.3-canary.1424.17806.0
  yarn add @auto-canary/slack@9.49.3-canary.1424.17806.0
  yarn add @auto-canary/twitter@9.49.3-canary.1424.17806.0
  yarn add @auto-canary/upload-assets@9.49.3-canary.1424.17806.0
  ```
</details>
<!-- GITHUB_RELEASE PR BODY: canary-version -->



Patch:
diff --git a/packages/core/package.json b/packages/core/package.json
index ce243f63..fced3b0d 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -64,6 +64,7 @@
     "node-fetch": "2.6.0",
     "parse-author": "^2.0.0",
     "parse-github-url": "1.0.2",
+    "pretty-ms": "^7.0.0",
     "semver": "^7.0.0",
     "signale": "^1.4.0",
     "tapable": "^2.0.0-beta.2",
diff --git a/packages/core/src/auto.ts b/packages/core/src/auto.ts
index cc6e4ea7..4b8900c0 100644
--- a/packages/core/src/auto.ts
+++ b/packages/core/src/auto.ts
@@ -1107,8 +1107,8 @@ export default class Auto {
     this.logger.verbose.info("Canary info found:", { pr, build });
 
     const from = (await this.git.shaExists("HEAD^")) ? "HEAD^" : "HEAD";
-    const head = await this.release.getCommitsInRelease(from);
-    const labels = head.map((commit) => commit.labels);
+    const commitsInRelease = await this.release.getCommitsInRelease(from);
+    const labels = commitsInRelease.map((commit) => commit.labels);
     let version = calculateSemVerBump(labels, this.semVerLabels!, this.config);
 
     if (version === SEMVER.noVersion) {
@@ -1210,15 +1210,6 @@ export default class Auto {
       await gitReset();
     }
 
-    let latestTag: string;
-
-    try {
-      latestTag = await this.git.getLatestTagInBranch();
-    } catch (error) {
-      latestTag = await this.git.getFirstCommit();
-    }
-
-    const commitsInRelease = await this.release.getCommits(latestTag);
     return { newVersion, commitsInRelease, context: "canary" };
   }
 
diff --git a/packages/core/src/git.ts b/packages/core/src/git.ts
index 28872463..003c9971 100644
--- a/packages/core/src/git.ts
+++ b/packages/core/src/git.ts
@@ -1,4 +1,3 @@
-import { graphql } from "@octokit/graphql";
 import { enterpriseCompatibility } from "@octokit/plugin-enterprise-compatibility";
 import path from "path";
 import { retry } from "@octokit/plugin-retry";
@@ -11,6 +10,7 @@ import endent from "endent";
 import on from "await-to-js";
 import join from "url-join";
 import { gt, lt } from "semver";
+import prettyMs from "pretty-ms";
 
 import { Memoize as memoize } from "typescript-memoize";
 
@@ -128,9 +128,7 @@ export default class Git {
       ? this.options.graphqlBaseUrl || join(new URL(this.baseUrl).origin, "api")
       : this.baseUrl;
     this.logger.veryVerbose.info(`Initializing GitHub with: ${this.baseUrl}`);
-    const GitHub = Octokit.plugin(enterpriseCompatibility)
-      .plugin(retry)
-      .plugin(throttling);
+    const GitHub = Octokit.plugin(enterpriseCompatibility, retry, throttling);
     this.github = new GitHub({
       baseUrl: this.baseUrl,
       auth: this.options.token,
@@ -144,15 +142,20 @@ export default class Git {
           );
 
           if (opts.request.retryCount < 5) {
-            this.logger.verbose.log(`Retrying after ${retryAfter} seconds!`);
+            this.logger.log.log(
+              `Retrying after ${prettyMs(retryAfter * 1000)}!`
+            );
             return true;
           }
         },
-        /** does not retry, only logs an error */
-        onAbuseLimit: (_: number, opts: ThrottleOpts) => {
+        /** wait after abuse */
+        onAbuseLimit: (retryAfter: number, opts: ThrottleOpts) => {
           this.logger.log.error(
-            `Went over abuse rate limit ${opts.method} ${opts.url}`
+            `Went over abuse rate limit ${opts.method} ${
+              opts.url
+            }, retrying in ${prettyMs(retryAfter * 1000)}.`
           );
+          return true;
         },
       },
     });
@@ -488,6 +491,7 @@ export default class Git {
   }
 
   /** Search to GitHub project's issue and pull requests */
+  @memoize()
   async searchRepo(
     options: RestEndpointMethodTypes["search"]["issuesAndPullRequests"]["parameters"]
   ) {
@@ -505,10 +509,11 @@ export default class Git {
   }
 
   /** Run a graphql query on the GitHub project */
+  @memoize()
   async graphql<T>(query: string) {
     this.logger.verbose.info("Querying Github using GraphQL:\n", query);
 
-    const data = await graphql<T>(query, {
+    const data = await this.github.graphql<T>(query, {
       baseUrl: this.graphqlBaseUrl,
       request: { agent: this.options.agent },
       headers: {
@@ -687,7 +692,7 @@ export default class Git {
     });
 
     this.logger.veryVerbose.info(`Got response from PR #${pr}\n`, result);
-    this.logger.verbose.info(`Got commits for PR #${pr}.`);
+    this.logger.verbose.info(`Got commits for PR #${pr}`);
 
     return result;
   }
diff --git a/packages/core/src/release.ts b/packages/core/src/release.ts
index a565cc77..9ec66473 100644
--- a/packages/core/src/release.ts
+++ b/packages/core/src/release.ts
@@ -283,6 +283,7 @@ export default class Release {
    * @param from - Tag or SHA to start at
    * @param to - Tag or SHA to end at (defaults to HEAD)
    */
+  @memoize()
   async getCommits(from: string, to = "HEAD"): Promise<IExtendedCommit[]> {
     this.logger.verbose.info(`Getting commits from ${from} to ${to}`);
 
@@ -611,12 +612,9 @@ export default class Release {
           hash: commit.hash,
         });
       } else if (commit.authorEmail) {
-        const author = await this.git.getUserByEmail(commit.authorEmail);
-
         resolvedAuthors.push({
           email: commit.authorEmail,
           name: commit.authorName,
-          ...author,
           hash: commit.hash,
         });
       }
diff --git a/plugins/first-time-contributor/src/index.ts b/plugins/first-time-contributor/src/index.ts
index 51b8c2fb..b7356c69 100644
--- a/plugins/first-time-contributor/src/index.ts
+++ b/plugins/first-time-contributor/src/index.ts
@@ -14,9 +14,6 @@ export default class FirstTimeContributorPlugin implements IPlugin {
 
   /** Tap into auto plugin points. */
   apply(auto: Auto) {
-    // eslint-disable-next-line @typescript-eslint/no-explicit-any
-    const cache: Record<string, Record<string, any>> = {};
-
     auto.hooks.onCreateChangelog.tap(this.name, (changelog) => {
       const base = new URL(changelog.options.baseUrl).origin;
 
@@ -26,46 +23,30 @@ export default class FirstTimeContributorPlugin implements IPlugin {
         return `${name}${username ? (name ? ` (${link})` : link) : ""}`;
       };
 
-      /** Get the PRs made by a user */
-      const getContributions = async (username: string) => {
-        if (cache[username]) {
-          return cache[username];
-        }
-
-        const response = await auto.git?.graphql<Record<string, any>>(`
-          {
-            search(first: 2, type: ISSUE, query: "repo:${auto.git?.options.owner}/${auto.git?.options.repo} is:pr is:merged author:${username}") {
-              issueCount
-            }
-          }
-        `);
-
-        if (response) {
-          cache[username] = response;
-        }
-
-        return response;
-      };
-
       changelog.hooks.addToBody.tapPromise(
         this.name,
         async (notes, commits) => {
-          const newContributors = (
-            await Promise.all(
-              flatMap(commits, (c) => c.authors).map(async (author) => {
-                if (!author.username || author.type === "Bot") {
-                  return;
-                }
+          const newContributors: ICommitAuthor[] = [];
 
-                // prettier-ignore
-                const prs = await getContributions(author.username)
+          for (const author of flatMap(commits, (c) => c.authors)) {
+            if (!author.username || author.type === "Bot") {
+              continue;
+            }
 
-                if (prs && prs.search.issueCount <= 1) {
-                  return author;
+            // prettier-ignore
+            // eslint-disable-next-line no-await-in-loop
+            const prs = await auto.git?.graphql<Record<string, any>>(`
+              {
+                search(first: 2, type: ISSUE, query: "repo:${auto.git?.options.owner}/${auto.git?.options.repo} is:pr is:merged author:${author.username}") {
+                  issueCount
                 }
-              })
-            )
-          ).filter((a): a is ICommitAuthor => Boolean(a));
+              }
+            `)
+
+            if (prs && prs.search.issueCount <= 1) {
+              newContributors.push(author);
+            }
+          }
 
           if (!newContributors.length) {
             return notes;
diff --git a/plugins/released/src/index.ts b/plugins/released/src/index.ts
index b462e1c4..b7ddb3e0 100644
--- a/plugins/released/src/index.ts
+++ b/plugins/released/src/index.ts
@@ -122,8 +122,8 @@ export default class ReleasedLabelPlugin implements IPlugin {
     const isPrerelease = releases.some((r) => r.data.prerelease);
 
     if (commit.pullRequest) {
-      const branch = (await auto.git?.getPullRequest(commit.pullRequest.number))
-        ?.data.head.ref;
+      const pr = await auto.git!.getPullRequest(commit.pullRequest.number);
+      const branch = pr?.data.head.ref;
 
       if (branch && auto.config?.prereleaseBranches.includes(branch)) {
         return;
@@ -136,7 +136,6 @@ export default class ReleasedLabelPlugin implements IPlugin {
         releases,
       });
 
-      const pr = await auto.git!.getPullRequest(commit.pullRequest.number);
       pr.data.body.split("\n").map((line) => messages.push(line));
 
       const commitsInPr = await auto.git!.getCommitsForPR(
diff --git a/yarn.lock b/yarn.lock
index 14d3bb83..536313b1 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4415,9 +4415,9 @@ bmp-js@^0.1.0:
   integrity sha1-4Fpj95amwf8l9Hcex62twUjAcjM=
 
 bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0:
-  version "4.11.9"
-  resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.9.tgz#26d556829458f9d1e81fc48952493d0ba3507828"
-  integrity sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==
+  version "4.11.8"
+  resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f"
+  integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==
 
 body-parser@1.19.0:
   version "1.19.0"
@@ -6890,9 +6890,9 @@ electron-to-chromium@^1.3.413:
   integrity sha512-K/lUeT3NLAsJ5SHRDhK3/zd0tw7OUllYD8w+fTOXm6ljCPsp2qq+vMzxpLo8u1M27ZjZAjRbsA6rirvne2nAMQ==
 
 elliptic@^6.0.0:
-  version "6.5.3"
-  resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.3.tgz#cb59eb2efdaf73a0bd78ccd7015a62ad6e0f93d6"
-  integrity sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==
+  version "6.4.1"
+  resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.1.tgz#c2d0b7776911b86722c632c3c06c60f2f819939a"
+  integrity sha512-BsXLz5sqX8OHcsh7CqBMztyXARmGQ3LWPtGjJi6DiJHq5C/qvi9P3OqgswKSDftbu8+IoI/QDTAm2fFnQ9SZSQ==
   dependencies:
     bn.js "^4.4.0"
     brorand "^1.0.1"
@@ -9519,20 +9519,20 @@ inflight@^1.0.4:
     once "^1.3.0"
     wrappy "1"
 
-inherits@2, inherits@2.0.4, inherits@^2.0.0, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3:
-  version "2.0.4"
-  resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
-  integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
+inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3:
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
+  integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
 
 inherits@2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1"
   integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=
 
-inherits@2.0.3:
-  version "2.0.3"
-  resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
-  integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
+inherits@2.0.4, inherits@^2.0.0:
+  version "2.0.4"
+  resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
+  integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
 
 ini@^1.3.2, ini@^1.3.4, ini@~1.3.0:
   version "1.3.5"
@@ -13328,6 +13328,11 @@ parse-json@^5.0.0:
     json-parse-better-errors "^1.0.1"
     lines-and-columns "^1.1.6"
 
+parse-ms@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/parse-ms/-/parse-ms-2.1.0.tgz#348565a753d4391fa524029956b172cb7753097d"
+  integrity sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==
+
 parse-numeric-range@^0.0.2:
   version "0.0.2"
   resolved "https://registry.yarnpkg.com/parse-numeric-range/-/parse-numeric-range-0.0.2.tgz#b4f09d413c7adbcd987f6e9233c7b4b210c938e4"
@@ -14186,6 +14191,13 @@ pretty-format@^26.1.0:
     ansi-styles "^4.0.0"
     react-is "^16.12.0"
 
+pretty-ms@^7.0.0:
+  version "7.0.0"
+  resolved "https://registry.yarnpkg.com/pretty-ms/-/pretty-ms-7.0.0.tgz#45781273110caf35f55cab21a8a9bd403a233dc0"
+  integrity sha512-J3aPWiC5e9ZeZFuSeBraGxSkGMOvulSWsxDByOcbD1Pr75YL3LSNIKIb52WXbCLE1sS5s4inBBbryjF4Y05Ceg==
+  dependencies:
+    parse-ms "^2.1.0"
+
 prismjs@~1.17.0:
   version "1.17.1"
   resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.17.1.tgz#e669fcbd4cdd873c35102881c33b14d0d68519be"


Test patch:
diff --git a/packages/cli/__tests__/main.test.ts b/packages/cli/__tests__/main.test.ts
index 1d04b076..77f05c69 100644
--- a/packages/cli/__tests__/main.test.ts
+++ b/packages/cli/__tests__/main.test.ts
@@ -2,6 +2,8 @@ import main, { run } from "../src/run";
 
 process.env.GH_TOKEN = "XXXX";
 
+jest.mock("@octokit/rest");
+
 test("throws error for unknown args", async () => {
   process.exit = jest.fn() as any;
   console.log = jest.fn() as any;
diff --git a/packages/core/src/__tests__/auto.test.ts b/packages/core/src/__tests__/auto.test.ts
index 23fa6f8e..dc44ee2e 100644
--- a/packages/core/src/__tests__/auto.test.ts
+++ b/packages/core/src/__tests__/auto.test.ts
@@ -1191,34 +1191,6 @@ describe("Auto", () => {
       expect(auto.git!.addToPrBody).toHaveBeenCalled();
     });
 
-    test("falls back to first commit", async () => {
-      const auto = new Auto({ ...defaults, plugins: [] });
-      // @ts-ignore
-      auto.checkClean = () => Promise.resolve(true);
-      auto.logger = dummyLog();
-      await auto.loadConfig();
-      auto.git!.getLatestTagInBranch = () => {
-        throw new Error();
-      };
-
-      auto.git!.getLatestRelease = () => Promise.resolve("1.2.3");
-      auto.git!.getSha = () => Promise.resolve("abc");
-      auto.release!.getCommits = () => Promise.resolve([]);
-      auto.git!.getFirstCommit = () => Promise.resolve("abcd");
-      jest.spyOn(auto.git!, "addToPrBody").mockImplementation();
-      jest
-        .spyOn(auto.release!, "getCommitsInRelease")
-        .mockImplementation()
-        .mockReturnValue(Promise.resolve([makeCommitFromMsg("Test Commit")]));
-      const canary = jest.fn();
-      canary.mockReturnValue("abcd");
-      auto.hooks.canary.tap("test", canary);
-      jest.spyOn(auto.release!, "getCommits").mockImplementation();
-
-      await auto.canary({ pr: 123, build: 1 });
-      expect(auto.release!.getCommits).toHaveBeenCalledWith("abcd");
-    });
-
     test("adds sha if no pr or build number is found", async () => {
       const auto = new Auto({ ...defaults, plugins: [] });
       // @ts-ignore
diff --git a/packages/core/src/__tests__/git.test.ts b/packages/core/src/__tests__/git.test.ts
index f8bc3b6a..42c60ee4 100644
--- a/packages/core/src/__tests__/git.test.ts
+++ b/packages/core/src/__tests__/git.test.ts
@@ -75,17 +75,15 @@ jest.mock("@octokit/rest", () => {
     hook = {
       error: errorHook,
     };
+
+    graphql = () => ({
+      data: [],
+    });
   };
 
   return { Octokit };
 });
 
-jest.mock("@octokit/graphql", () => ({
-  graphql: () => ({
-    data: [],
-  }),
-}));
-
 const options = {
   owner: "Adam Dierkens",
   repo: "test",
diff --git a/packages/core/src/__tests__/release.test.ts b/packages/core/src/__tests__/release.test.ts
index 5df1b7ff..16ccfec0 100644
--- a/packages/core/src/__tests__/release.test.ts
+++ b/packages/core/src/__tests__/release.test.ts
@@ -1049,7 +1049,7 @@ describe("Release", () => {
       expect(await gh.getSemverBump("1234", "123")).toBe(SEMVER.minor);
     });
 
-    test("should be able to configure labels", async () => {
+    test("should be able to configure labels - no release", async () => {
       const customLabels = [
         ...defaultLabels,
         { name: "Version: Major", releaseType: SEMVER.major },
@@ -1083,7 +1083,30 @@ describe("Release", () => {
       );
 
       expect(await gh.getSemverBump("1234", "123")).toBe("");
+    });
+
+    test("should be able to configure labels", async () => {
+      const customLabels = [
+        ...defaultLabels,
+        { name: "Version: Major", releaseType: SEMVER.major },
+        { name: "Version: Minor", releaseType: SEMVER.minor },
+        { name: "Version: Patch", releaseType: SEMVER.patch },
+        { name: "Deploy", releaseType: "release" },
+      ] as ILabelDefinition[];
+
+      const gh = new Release(git, {
+        onlyPublishWithReleaseLabel: true,
+        prereleaseBranches: ["next"],
+        labels: customLabels,
+        baseBranch: "master",
+      });
+      const commits = [
+        makeCommitFromMsg("First (#1234)"),
+        makeCommitFromMsg("Second (#1235)"),
+        makeCommitFromMsg("Third (#1236)"),
+      ];
 
+      // Test deploy label creates release
       getGitLog.mockReturnValueOnce(commits);
       getPr.mockReturnValueOnce(
         Promise.resolve(mockLabels(["Version: Minor", "Deploy"]))