Repository: semantic-release/github (js)

Issue:
API rate limit exceeded for installation
I was hoping that rate limiting is now properly taken care of since we moved to Ocotkit via https://github.com/semantic-release/github/pull/487, but the error popped up again

https://github.com/semantic-release/commit-analyzer/actions/runs/5159013164/jobs/9293389281#step:6:126

We can tell from the user agent that the correct `@semantic-release/github` version was used

```
      'user-agent': '@semantic-release/github v8.1.0 octokit-core.js/4.2.1 Node.js/18.16.0 (linux; x64)',
```

What's odd: the `request` option does not have a `retries` key, it looks like it didn't attempt to retry at all:
https://github.com/semantic-release/commit-analyzer/actions/runs/5159013164/jobs/9293389281#step:6:138

I will look into it. Sorry, I'm not sure why this is happening and rate limit errors are hard to reproduce reliably, it will take me a moment to get to the bottom of this. If anyone is inclined to help out, you are very welcome to

Pull request description:
fix: replace github search api with graphql in success lifecycle method
This Pull Request refactors the `success` lifecycle function replacing the usage of Github "Search API" with "GraphQL API".

#### Changes Made

- Added `buildAssociatedPRsQuery` helper function to which builds GraphQL query for fetching associated PRs to a list of commit hash (sha). It does this by collecting a list of `shas` which maps down to a list of strings that leverages the GraphQL `fragment` enabling ability to request data of multiple commits in one request using `GitObject`. 

    ```js
    /**
     * Builds GraphQL query for fetching associated PRs to a list of commit hash (sha)
     * @param {Array<string>} shas
     * @returns {string}
     */
    export function buildAssociatedPRsQuery(shas) {
      return `#graphql
        query getAssociatedPRs($owner: String!, $repo: String!) {
          repository(owner: $owner, name: $repo) {
            ${shas
              .map((sha) => {
                return `commit${sha.slice(0, 6)}: object(oid: "${sha}") {
                ...on Commit {
                  associatedPullRequests(first: 100) {
                    nodes {
                      url
                      number
                      body
                    }
                  }
                }
              }`;
              })
              .join("")}
          }
        }
      `;
    }
    ```
- Replaced the instance of the search api consumption with the `getSearchQueries` method integration with the graphql api integration, which was used to retrieve all associated pull requests.

    ```js
    const { repository } = await octokit.graphql(
      buildAssociatedPRsQuery(shas),
      { owner, repo },
    );
    const associatedPRs = Object.values(repository).map(
      (item) => item.associatedPullRequests.nodes,
    );
    ```
- Modified test suites in acknowledgement of the changes..

### Related Issue

Fixes #644


### Screencast/Screenshot

[screencast-bpconcjcammlapcogcnnelfmaeghhagj-2024.06.24-14_54_49.webm](https://github.com/semantic-release/github/assets/25631971/0135e31b-baa2-436c-b4e1-9abddffc60e5)



Patch:
diff --git a/lib/get-search-queries.js b/lib/get-search-queries.js
deleted file mode 100644
index 4126b1e..0000000
--- a/lib/get-search-queries.js
+++ /dev/null
@@ -1,16 +0,0 @@
-export default function getSearchQueries(base, commits, separator = "+") {
-  return commits.reduce((searches, commit) => {
-    const lastSearch = searches[searches.length - 1];
-
-    if (
-      lastSearch &&
-      lastSearch.length + commit.length <= 256 - separator.length
-    ) {
-      searches[searches.length - 1] = `${lastSearch}${separator}${commit}`;
-    } else {
-      searches.push(`${base}${separator}${commit}`);
-    }
-
-    return searches;
-  }, []);
-}
diff --git a/lib/success.js b/lib/success.js
index 8672990..49ad22e 100644
--- a/lib/success.js
+++ b/lib/success.js
@@ -7,7 +7,6 @@ import debugFactory from "debug";
 import parseGithubUrl from "./parse-github-url.js";
 import resolveConfig from "./resolve-config.js";
 import { toOctokitOptions } from "./octokit.js";
-import getSearchQueries from "./get-search-queries.js";
 import getSuccessComment from "./get-success-comment.js";
 import findSRIssues from "./find-sr-issues.js";
 import { RELEASE_NAME } from "./definitions/constants.js";
@@ -65,44 +64,38 @@ export default async function success(pluginConfig, context, { Octokit }) {
     const releaseInfos = releases.filter((release) => Boolean(release.name));
     const shas = commits.map(({ hash }) => hash);
 
-    const searchQueries = getSearchQueries(
-      `repo:${owner}/${repo}+type:pr+is:merged`,
-      shas,
-    ).map(
-      async (q) =>
-        (await octokit.request("GET /search/issues", { q })).data.items,
+    const { repository } = await octokit.graphql(
+      buildAssociatedPRsQuery(shas),
+      { owner, repo },
     );
-
-    const searchQueriesResults = await Promise.all(searchQueries);
-    const uniqueSearchQueriesResults = uniqBy(
-      flatten(searchQueriesResults),
-      "number",
+    const associatedPRs = Object.values(repository).map(
+      (item) => item.associatedPullRequests.nodes,
     );
-    const prs = await pFilter(
-      uniqueSearchQueriesResults,
-      async ({ number }) => {
-        const commits = await octokit.paginate(
-          "GET /repos/{owner}/{repo}/pulls/{pull_number}/commits",
-          {
-            owner,
-            repo,
-            pull_number: number,
-          },
-        );
-        const matchingCommit = commits.find(({ sha }) => shas.includes(sha));
-        if (matchingCommit) return matchingCommit;
 
-        const { data: pullRequest } = await octokit.request(
-          "GET /repos/{owner}/{repo}/pulls/{pull_number}",
-          {
-            owner,
-            repo,
-            pull_number: number,
-          },
-        );
-        return shas.includes(pullRequest.merge_commit_sha);
-      },
-    );
+    const uniqueAssociatedPRs = uniqBy(flatten(associatedPRs), "number");
+
+    const prs = await pFilter(uniqueAssociatedPRs, async ({ number }) => {
+      const commits = await octokit.paginate(
+        "GET /repos/{owner}/{repo}/pulls/{pull_number}/commits",
+        {
+          owner,
+          repo,
+          pull_number: number,
+        },
+      );
+      const matchingCommit = commits.find(({ sha }) => shas.includes(sha));
+      if (matchingCommit) return matchingCommit;
+
+      const { data: pullRequest } = await octokit.request(
+        "GET /repos/{owner}/{repo}/pulls/{pull_number}",
+        {
+          owner,
+          repo,
+          pull_number: number,
+        },
+      );
+      return shas.includes(pullRequest.merge_commit_sha);
+    });
 
     debug(
       "found pull requests: %O",
@@ -250,3 +243,32 @@ export default async function success(pluginConfig, context, { Octokit }) {
     throw new AggregateError(errors);
   }
 }
+
+/**
+ * Builds GraphQL query for fetching associated PRs to a list of commit hash (sha)
+ * @param {Array<string>} shas
+ * @returns {string}
+ */
+export function buildAssociatedPRsQuery(shas) {
+  return `#graphql
+    query getAssociatedPRs($owner: String!, $repo: String!) {
+      repository(owner: $owner, name: $repo) {
+        ${shas
+          .map((sha) => {
+            return `commit${sha.slice(0, 6)}: object(oid: "${sha}") {
+            ...on Commit {
+              associatedPullRequests(first: 100) {
+                nodes {
+                  url
+                  number
+                  body
+                }
+              }
+            }
+          }`;
+          })
+          .join("")}
+      }
+    }
+  `;
+}


Test patch:
diff --git a/test/get-search-queries.test.js b/test/get-search-queries.test.js
deleted file mode 100644
index 104bd77..0000000
--- a/test/get-search-queries.test.js
+++ /dev/null
@@ -1,41 +0,0 @@
-import test from "ava";
-import { repeat } from "lodash-es";
-
-import getSearchQueries from "../lib/get-search-queries.js";
-
-test("Generate queries of 256 characters maximum", (t) => {
-  const commits = [
-    repeat("a", 40),
-    repeat("b", 40),
-    repeat("c", 40),
-    repeat("d", 40),
-    repeat("e", 40),
-    repeat("f", 40),
-  ];
-
-  t.deepEqual(getSearchQueries(repeat("0", 51), commits), [
-    `${repeat("0", 51)}+${commits[0]}+${commits[1]}+${commits[2]}+${
-      commits[3]
-    }+${commits[4]}`,
-    `${repeat("0", 51)}+${commits[5]}`,
-  ]);
-
-  t.deepEqual(getSearchQueries(repeat("0", 52), commits), [
-    `${repeat("0", 52)}+${commits[0]}+${commits[1]}+${commits[2]}+${
-      commits[3]
-    }`,
-    `${repeat("0", 52)}+${commits[4]}+${commits[5]}`,
-  ]);
-});
-
-test("Generate one query if it is less than 256 characters", (t) => {
-  const commits = [repeat("a", 40), repeat("b", 40)];
-
-  t.deepEqual(getSearchQueries(repeat("0", 20), commits), [
-    `${repeat("0", 20)}+${commits[0]}+${commits[1]}`,
-  ]);
-});
-
-test("Return emty Array if there is no commits", (t) => {
-  t.deepEqual(getSearchQueries("base", []), []);
-});
diff --git a/test/integration.test.js b/test/integration.test.js
index 3887709..b9f2f29 100644
--- a/test/integration.test.js
+++ b/test/integration.test.js
@@ -432,14 +432,17 @@ test("Comment and add labels on PR included in the releases", async (t) => {
         repeat: 2,
       },
     )
-    .getOnce(
-      `https://api.github.local/search/issues?q=${encodeURIComponent(
-        `repo:${owner}/${repo}`,
-      )}+${encodeURIComponent("type:pr")}+${encodeURIComponent(
-        "is:merged",
-      )}+${commits.map((commit) => commit.hash).join("+")}`,
-      { items: prs },
-    )
+    .postOnce("https://api.github.local/graphql", {
+      data: {
+        repository: {
+          commit123: {
+            associatedPullRequests: {
+              nodes: [prs[0]],
+            },
+          },
+        },
+      },
+    })
     .getOnce(
       `https://api.github.local/repos/${owner}/${repo}/pulls/1/commits`,
       [{ sha: commits[0].hash }],
@@ -649,14 +652,17 @@ test("Verify, release and notify success", async (t) => {
       { html_url: releaseUrl },
       { body: { draft: false } },
     )
-    .getOnce(
-      `https://api.github.local/search/issues?q=${encodeURIComponent(
-        `repo:${owner}/${repo}`,
-      )}+${encodeURIComponent("type:pr")}+${encodeURIComponent(
-        "is:merged",
-      )}+${commits.map((commit) => commit.hash).join("+")}`,
-      { items: prs },
-    )
+    .postOnce("https://api.github.local/graphql", {
+      data: {
+        repository: {
+          commit123: {
+            associatedPullRequests: {
+              nodes: [prs[0]],
+            },
+          },
+        },
+      },
+    })
     .getOnce(
       `https://api.github.local/repos/${owner}/${repo}/pulls/1/commits`,
       [{ sha: commits[0].hash }],
@@ -686,7 +692,6 @@ test("Verify, release and notify success", async (t) => {
         browser_download_url: otherAssetUrl,
       },
     )
-
     .postOnce(
       `https://api.github.local/repos/${owner}/${repo}/issues/1/comments`,
       {
@@ -800,14 +805,17 @@ test("Verify, update release and notify success", async (t) => {
         },
       },
     )
-    .getOnce(
-      `https://api.github.local/search/issues?q=${encodeURIComponent(
-        `repo:${owner}/${repo}`,
-      )}+${encodeURIComponent("type:pr")}+${encodeURIComponent(
-        "is:merged",
-      )}+${commits.map((commit) => commit.hash).join("+")}`,
-      { items: prs },
-    )
+    .postOnce("https://api.github.local/graphql", {
+      data: {
+        repository: {
+          commit123: {
+            associatedPullRequests: {
+              nodes: [prs[0]],
+            },
+          },
+        },
+      },
+    })
     .getOnce(
       `https://api.github.local/repos/${owner}/${repo}/pulls/1/commits`,
       [{ sha: commits[0].hash }],
diff --git a/test/success.test.js b/test/success.test.js
index da1c58a..5ef815a 100644
--- a/test/success.test.js
+++ b/test/success.test.js
@@ -2,6 +2,7 @@ import { repeat } from "lodash-es";
 import sinon from "sinon";
 import test from "ava";
 import fetchMock from "fetch-mock";
+import assert from "assert";
 
 import { ISSUE_ID } from "../lib/definitions/constants.js";
 import getReleaseLinks from "../lib/get-release-links.js";
@@ -57,14 +58,22 @@ test("Add comment and labels to PRs associated with release commits and issues s
     .getOnce(`https://api.github.local/repos/${owner}/${repo}`, {
       full_name: `${redirectedOwner}/${redirectedRepo}`,
     })
-    .getOnce(
-      `https://api.github.local/search/issues?q=${encodeURIComponent(
-        `repo:${redirectedOwner}/${redirectedRepo}`,
-      )}+${encodeURIComponent("type:pr")}+${encodeURIComponent(
-        "is:merged",
-      )}+${commits.map((commit) => commit.hash).join("+")}`,
-      { items: prs },
-    )
+    .postOnce("https://api.github.local/graphql", {
+      data: {
+        repository: {
+          commit123: {
+            associatedPullRequests: {
+              nodes: [prs[0]],
+            },
+          },
+          commit456: {
+            associatedPullRequests: {
+              nodes: [prs[1]],
+            },
+          },
+        },
+      },
+    })
     .getOnce(
       `https://api.github.local/repos/${redirectedOwner}/${redirectedRepo}/pulls/1/commits`,
       [{ sha: commits[0].hash }],
@@ -219,14 +228,22 @@ test("Add comment and labels to PRs associated with release commits and issues c
     .getOnce(`https://custom-url.com/prefix/repos/${owner}/${repo}`, {
       full_name: `${owner}/${repo}`,
     })
-    .getOnce(
-      `https://custom-url.com/prefix/search/issues?q=${encodeURIComponent(
-        `repo:${owner}/${repo}`,
-      )}+${encodeURIComponent("type:pr")}+${encodeURIComponent(
-        "is:merged",
-      )}+${commits.map((commit) => commit.hash).join("+")}`,
-      { items: prs },
-    )
+    .postOnce("https://custom-url.com/prefix/graphql", {
+      data: {
+        repository: {
+          commit123: {
+            associatedPullRequests: {
+              nodes: [prs[0]],
+            },
+          },
+          commit456: {
+            associatedPullRequests: {
+              nodes: [prs[1]],
+            },
+          },
+        },
+      },
+    })
     .getOnce(
       `https://custom-url.com/prefix/repos/${owner}/${repo}/pulls/1/commits`,
       [{ sha: commits[0].hash }],
@@ -402,24 +419,42 @@ test("Make multiple search queries if necessary", async (t) => {
     .getOnce(`https://api.github.local/repos/${owner}/${repo}`, {
       full_name: `${owner}/${repo}`,
     })
-    .getOnce(
-      `https://api.github.local/search/issues?q=${encodeURIComponent(
-        `repo:${owner}/${repo}`,
-      )}+${encodeURIComponent("type:pr")}+${encodeURIComponent("is:merged")}+${
-        commits[0].hash
-      }+${commits[1].hash}+${commits[2].hash}+${commits[3].hash}+${
-        commits[4].hash
-      }`,
-      { items: [prs[0], prs[1], prs[2], prs[3], prs[4]] },
-    )
-    .getOnce(
-      `https://api.github.local/search/issues?q=${encodeURIComponent(
-        `repo:${owner}/${repo}`,
-      )}+${encodeURIComponent("type:pr")}+${encodeURIComponent("is:merged")}+${
-        commits[5].hash
-      }+${commits[6].hash}`,
-      { items: [prs[5], prs[1]] },
-    )
+    .post("https://api.github.local/graphql", {
+      data: {
+        repository: {
+          commitaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: {
+            associatedPullRequests: {
+              nodes: [prs[0]],
+            },
+          },
+          commitbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb: {
+            associatedPullRequests: {
+              nodes: [prs[1]],
+            },
+          },
+          commitcccccccccccccccccccccccccccccccccccccccccc: {
+            associatedPullRequests: {
+              nodes: [prs[2]],
+            },
+          },
+          commiteeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee: {
+            associatedPullRequests: {
+              nodes: [prs[3]],
+            },
+          },
+          commitffffffffffffffffffffffffffffffffffffffffff: {
+            associatedPullRequests: {
+              nodes: [prs[4]],
+            },
+          },
+          commitgggggggggggggggggggggggggggggggggggggggggg: {
+            associatedPullRequests: {
+              nodes: [prs[5]],
+            },
+          },
+        },
+      },
+    })
     .getOnce(
       `https://api.github.local/repos/${owner}/${repo}/pulls/1/commits`,
       [{ sha: commits[0].hash }],
@@ -628,14 +663,22 @@ test("Do not add comment and labels for unrelated PR returned by search (compare
     .getOnce(`https://api.github.local/repos/${owner}/${repo}`, {
       full_name: `${owner}/${repo}`,
     })
-    .getOnce(
-      `https://api.github.local/search/issues?q=${encodeURIComponent(
-        `repo:${owner}/${repo}`,
-      )}+${encodeURIComponent("type:pr")}+${encodeURIComponent(
-        "is:merged",
-      )}+${commits.map((commit) => commit.hash).join("+")}`,
-      { items: prs },
-    )
+    .postOnce("https://api.github.local/graphql", {
+      data: {
+        repository: {
+          commit123: {
+            associatedPullRequests: {
+              nodes: [prs[0]],
+            },
+          },
+          commit456: {
+            associatedPullRequests: {
+              nodes: [prs[1]],
+            },
+          },
+        },
+      },
+    })
     .getOnce(
       `https://api.github.local/repos/${owner}/${repo}/pulls/1/commits`,
       [{ sha: "rebased_sha" }],
@@ -722,14 +765,17 @@ test("Do not add comment and labels if no PR is associated with release commits"
     .getOnce(`https://api.github.local/repos/${owner}/${repo}`, {
       full_name: `${owner}/${repo}`,
     })
-    .getOnce(
-      `https://api.github.local/search/issues?q=${encodeURIComponent(
-        `repo:${owner}/${repo}`,
-      )}+${encodeURIComponent("type:pr")}+${encodeURIComponent(
-        "is:merged",
-      )}+${commits.map((commit) => commit.hash).join("+")}`,
-      { items: [] },
-    )
+    .postOnce("https://api.github.local/graphql", {
+      data: {
+        repository: {
+          commit123: {
+            associatedPullRequests: {
+              nodes: [],
+            },
+          },
+        },
+      },
+    })
     .getOnce(
       `https://api.github.local/search/issues?q=${encodeURIComponent(
         "in:title",
@@ -785,14 +831,27 @@ test("Do not add comment and labels to PR/issues from other repo", async (t) =>
     .getOnce(`https://api.github.local/repos/${owner}/${repo}`, {
       full_name: `${owner}/${repo}`,
     })
-    .getOnce(
-      `https://api.github.local/search/issues?q=${encodeURIComponent(
-        `repo:${owner}/${repo}`,
-      )}+${encodeURIComponent("type:pr")}+${encodeURIComponent(
-        "is:merged",
-      )}+${commits.map((commit) => commit.hash).join("+")}`,
-      { items: [] },
-    )
+    .postOnce("https://api.github.local/graphql", {
+      data: {
+        repository: {
+          commit123: {
+            associatedPullRequests: {
+              nodes: [],
+            },
+          },
+          commit456: {
+            associatedPullRequests: {
+              nodes: [],
+            },
+          },
+          commit789: {
+            associatedPullRequests: {
+              nodes: [],
+            },
+          },
+        },
+      },
+    })
     .postOnce(
       `https://api.github.local/repos/${owner}/${repo}/issues/2/comments`,
       { html_url: "https://github.com/successcomment-2" },
@@ -872,14 +931,27 @@ test("Ignore missing and forbidden issues/PRs", async (t) => {
     .getOnce(`https://api.github.local/repos/${owner}/${repo}`, {
       full_name: `${owner}/${repo}`,
     })
-    .getOnce(
-      `https://api.github.local/search/issues?q=${encodeURIComponent(
-        `repo:${owner}/${repo}`,
-      )}+${encodeURIComponent("type:pr")}+${encodeURIComponent(
-        "is:merged",
-      )}+${commits.map((commit) => commit.hash).join("+")}`,
-      { items: prs },
-    )
+    .postOnce("https://api.github.local/graphql", {
+      data: {
+        repository: {
+          commit123: {
+            associatedPullRequests: {
+              nodes: [prs[0]],
+            },
+          },
+          commit456: {
+            associatedPullRequests: {
+              nodes: [prs[1]],
+            },
+          },
+          commit789: {
+            associatedPullRequests: {
+              nodes: [prs[2]],
+            },
+          },
+        },
+      },
+    })
     .getOnce(
       `https://api.github.local/repos/${owner}/${repo}/pulls/1/commits`,
       [{ sha: commits[0].hash }],
@@ -1028,14 +1100,17 @@ test("Add custom comment and labels", async (t) => {
     .getOnce(`https://api.github.local/repos/${owner}/${repo}`, {
       full_name: `${owner}/${repo}`,
     })
-    .getOnce(
-      `https://api.github.local/search/issues?q=${encodeURIComponent(
-        `repo:${owner}/${repo}`,
-      )}+${encodeURIComponent("type:pr")}+${encodeURIComponent(
-        "is:merged",
-      )}+${commits.map((commit) => commit.hash).join("+")}`,
-      { items: prs },
-    )
+    .postOnce("https://api.github.local/graphql", {
+      data: {
+        repository: {
+          commit123: {
+            associatedPullRequests: {
+              nodes: [prs[0]],
+            },
+          },
+        },
+      },
+    })
     .getOnce(
       `https://api.github.local/repos/${owner}/${repo}/pulls/1/commits`,
       [{ sha: commits[0].hash }],
@@ -1120,14 +1195,17 @@ test("Add custom label", async (t) => {
     .getOnce(`https://api.github.local/repos/${owner}/${repo}`, {
       full_name: `${owner}/${repo}`,
     })
-    .getOnce(
-      `https://api.github.local/search/issues?q=${encodeURIComponent(
-        `repo:${owner}/${repo}`,
-      )}+${encodeURIComponent("type:pr")}+${encodeURIComponent(
-        "is:merged",
-      )}+${commits.map((commit) => commit.hash).join("+")}`,
-      { items: prs },
-    )
+    .postOnce("https://api.github.local/graphql", {
+      data: {
+        repository: {
+          commit123: {
+            associatedPullRequests: {
+              nodes: [prs[0]],
+            },
+          },
+        },
+      },
+    })
     .getOnce(
       `https://api.github.local/repos/${owner}/${repo}/pulls/1/commits`,
       [{ sha: commits[0].hash }],
@@ -1207,14 +1285,17 @@ test("Comment on issue/PR without ading a label", async (t) => {
     .getOnce(`https://api.github.local/repos/${owner}/${repo}`, {
       full_name: `${owner}/${repo}`,
     })
-    .getOnce(
-      `https://api.github.local/search/issues?q=${encodeURIComponent(
-        `repo:${owner}/${repo}`,
-      )}+${encodeURIComponent("type:pr")}+${encodeURIComponent(
-        "is:merged",
-      )}+${commits.map((commit) => commit.hash).join("+")}`,
-      { items: prs },
-    )
+    .postOnce("https://api.github.local/graphql", {
+      data: {
+        repository: {
+          commit123: {
+            associatedPullRequests: {
+              nodes: [prs[0]],
+            },
+          },
+        },
+      },
+    })
     .getOnce(
       `https://api.github.local/repos/${owner}/${repo}/pulls/1/commits`,
       [{ sha: commits[0].hash }],
@@ -1297,14 +1378,17 @@ test("Editing the release to include all release links at the bottom", async (t)
     .getOnce(`https://api.github.local/repos/${owner}/${repo}`, {
       full_name: `${owner}/${repo}`,
     })
-    .getOnce(
-      `https://api.github.local/search/issues?q=${encodeURIComponent(
-        `repo:${owner}/${repo}`,
-      )}+${encodeURIComponent("type:pr")}+${encodeURIComponent(
-        "is:merged",
-      )}+${commits.map((commit) => commit.hash).join("+")}`,
-      { items: prs },
-    )
+    .postOnce("https://api.github.local/graphql", {
+      data: {
+        repository: {
+          commit123: {
+            associatedPullRequests: {
+              nodes: [prs[0]],
+            },
+          },
+        },
+      },
+    })
     .getOnce(
       `https://api.github.local/repos/${owner}/${repo}/pulls/1/commits`,
       [{ sha: commits[0].hash }],
@@ -1398,14 +1482,17 @@ test("Editing the release to include all release links at the top", async (t) =>
     .getOnce(`https://api.github.local/repos/${owner}/${repo}`, {
       full_name: `${owner}/${repo}`,
     })
-    .getOnce(
-      `https://api.github.local/search/issues?q=${encodeURIComponent(
-        `repo:${owner}/${repo}`,
-      )}+${encodeURIComponent("type:pr")}+${encodeURIComponent(
-        "is:merged",
-      )}+${commits.map((commit) => commit.hash).join("+")}`,
-      { items: prs },
-    )
+    .postOnce("https://api.github.local/graphql", {
+      data: {
+        repository: {
+          commit123: {
+            associatedPullRequests: {
+              nodes: [prs[0]],
+            },
+          },
+        },
+      },
+    })
     .getOnce(
       `https://api.github.local/repos/${owner}/${repo}/pulls/1/commits`,
       [{ sha: commits[0].hash }],
@@ -1496,14 +1583,17 @@ test("Editing the release to include all release links with no additional releas
     .getOnce(`https://api.github.local/repos/${owner}/${repo}`, {
       full_name: `${owner}/${repo}`,
     })
-    .getOnce(
-      `https://api.github.local/search/issues?q=${encodeURIComponent(
-        `repo:${owner}/${repo}`,
-      )}+${encodeURIComponent("type:pr")}+${encodeURIComponent(
-        "is:merged",
-      )}+${commits.map((commit) => commit.hash).join("+")}`,
-      { items: prs },
-    )
+    .postOnce("https://api.github.local/graphql", {
+      data: {
+        repository: {
+          commit123: {
+            associatedPullRequests: {
+              nodes: [prs[0]],
+            },
+          },
+        },
+      },
+    })
     .getOnce(
       `https://api.github.local/repos/${owner}/${repo}/pulls/1/commits`,
       [{ sha: commits[0].hash }],
@@ -1583,14 +1673,17 @@ test("Editing the release to include all release links with no additional releas
     .getOnce(`https://api.github.local/repos/${owner}/${repo}`, {
       full_name: `${owner}/${repo}`,
     })
-    .getOnce(
-      `https://api.github.local/search/issues?q=${encodeURIComponent(
-        `repo:${owner}/${repo}`,
-      )}+${encodeURIComponent("type:pr")}+${encodeURIComponent(
-        "is:merged",
-      )}+${commits.map((commit) => commit.hash).join("+")}`,
-      { items: prs },
-    )
+    .postOnce("https://api.github.local/graphql", {
+      data: {
+        repository: {
+          commit123: {
+            associatedPullRequests: {
+              nodes: [prs[0]],
+            },
+          },
+        },
+      },
+    })
     .getOnce(
       `https://api.github.local/repos/${owner}/${repo}/pulls/1/commits`,
       [{ sha: commits[0].hash }],
@@ -1663,14 +1756,17 @@ test("Editing the release to include all release links with no releases", async
     .getOnce(`https://api.github.local/repos/${owner}/${repo}`, {
       full_name: `${owner}/${repo}`,
     })
-    .getOnce(
-      `https://api.github.local/search/issues?q=${encodeURIComponent(
-        `repo:${owner}/${repo}`,
-      )}+${encodeURIComponent("type:pr")}+${encodeURIComponent(
-        "is:merged",
-      )}+${commits.map((commit) => commit.hash).join("+")}`,
-      { items: prs },
-    )
+    .postOnce("https://api.github.local/graphql", {
+      data: {
+        repository: {
+          commit123: {
+            associatedPullRequests: {
+              nodes: [prs[0]],
+            },
+          },
+        },
+      },
+    })
     .getOnce(
       `https://api.github.local/repos/${owner}/${repo}/pulls/1/commits`,
       [{ sha: commits[0].hash }],
@@ -1745,14 +1841,17 @@ test("Editing the release with no ID in the release", async (t) => {
     .getOnce(`https://api.github.local/repos/${owner}/${repo}`, {
       full_name: `${owner}/${repo}`,
     })
-    .getOnce(
-      `https://api.github.local/search/issues?q=${encodeURIComponent(
-        `repo:${owner}/${repo}`,
-      )}+${encodeURIComponent("type:pr")}+${encodeURIComponent(
-        "is:merged",
-      )}+${commits.map((commit) => commit.hash).join("+")}`,
-      { items: prs },
-    )
+    .postOnce("https://api.github.local/graphql", {
+      data: {
+        repository: {
+          commit123: {
+            associatedPullRequests: {
+              nodes: [prs[0]],
+            },
+          },
+        },
+      },
+    })
     .getOnce(
       `https://api.github.local/repos/${owner}/${repo}/pulls/1/commits`,
       [{ sha: commits[0].hash }],
@@ -1832,14 +1931,22 @@ test("Ignore errors when adding comments and closing issues", async (t) => {
     .getOnce(`https://api.github.local/repos/${owner}/${repo}`, {
       full_name: `${owner}/${repo}`,
     })
-    .getOnce(
-      `https://api.github.local/search/issues?q=${encodeURIComponent(
-        `repo:${owner}/${repo}`,
-      )}+${encodeURIComponent("type:pr")}+${encodeURIComponent(
-        "is:merged",
-      )}+${commits.map((commit) => commit.hash).join("+")}`,
-      { items: prs },
-    )
+    .postOnce("https://api.github.local/graphql", {
+      data: {
+        repository: {
+          commit123: {
+            associatedPullRequests: {
+              nodes: [prs[0]],
+            },
+          },
+          commit456: {
+            associatedPullRequests: {
+              nodes: [prs[1]],
+            },
+          },
+        },
+      },
+    })
     .getOnce(
       `https://api.github.local/repos/${owner}/${repo}/pulls/1/commits`,
       [{ sha: commits[0].hash }],
@@ -1957,14 +2064,17 @@ test("Close open issues when a release is successful", async (t) => {
     .getOnce(`https://api.github.local/repos/${owner}/${repo}`, {
       full_name: `${owner}/${repo}`,
     })
-    .getOnce(
-      `https://api.github.local/search/issues?q=${encodeURIComponent(
-        `repo:${owner}/${repo}`,
-      )}+${encodeURIComponent("type:pr")}+${encodeURIComponent(
-        "is:merged",
-      )}+${commits.map((commit) => commit.hash).join("+")}`,
-      { items: [] },
-    )
+    .postOnce("https://api.github.local/graphql", {
+      data: {
+        repository: {
+          commit123: {
+            associatedPullRequests: {
+              nodes: [],
+            },
+          },
+        },
+      },
+    })
     .getOnce(
       `https://api.github.local/search/issues?q=${encodeURIComponent(
         "in:title",
@@ -2105,14 +2215,17 @@ test('Skip closing issues if "failComment" is "false"', async (t) => {
     .getOnce(`https://api.github.local/repos/${owner}/${repo}`, {
       full_name: `${owner}/${repo}`,
     })
-    .getOnce(
-      `https://api.github.local/search/issues?q=${encodeURIComponent(
-        `repo:${owner}/${repo}`,
-      )}+${encodeURIComponent("type:pr")}+${encodeURIComponent(
-        "is:merged",
-      )}+${commits.map((commit) => commit.hash).join("+")}`,
-      { items: [] },
-    );
+    .postOnce("https://api.github.local/graphql", {
+      data: {
+        repository: {
+          commit123: {
+            associatedPullRequests: {
+              nodes: [],
+            },
+          },
+        },
+      },
+    });
 
   await success(
     pluginConfig,
@@ -2153,14 +2266,17 @@ test('Skip closing issues if "failTitle" is "false"', async (t) => {
     .getOnce(`https://api.github.local/repos/${owner}/${repo}`, {
       full_name: `${owner}/${repo}`,
     })
-    .getOnce(
-      `https://api.github.local/search/issues?q=${encodeURIComponent(
-        `repo:${owner}/${repo}`,
-      )}+${encodeURIComponent("type:pr")}+${encodeURIComponent(
-        "is:merged",
-      )}+${commits.map((commit) => commit.hash).join("+")}`,
-      { items: [] },
-    );
+    .postOnce("https://api.github.local/graphql", {
+      data: {
+        repository: {
+          commit123: {
+            associatedPullRequests: {
+              nodes: [],
+            },
+          },
+        },
+      },
+    });
 
   await success(
     pluginConfig,