diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 83878b26..803cdc7e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,9 +19,9 @@ jobs: strategy: matrix: node-version: - - 20.8.1 - - 20 - - 21 + - 22.14.0 + - 24.3.0 + - 24 os: - ubuntu-latest runs-on: "${{ matrix.os }}" diff --git a/lib/find-sr-issues.js b/lib/find-sr-issues.js index 47af4104..10a52716 100644 --- a/lib/find-sr-issues.js +++ b/lib/find-sr-issues.js @@ -1,7 +1,7 @@ import { uniqBy } from "lodash-es"; import { ISSUE_ID, RELEASE_FAIL_LABEL } from "./definitions/constants.js"; -export default async (octokit, logger, title, labels, owner, repo) => { +export default async (octokit, logger, labels, owner, repo) => { let issues = []; const { @@ -18,25 +18,6 @@ export default async (octokit, logger, title, labels, owner, repo) => { issues.push(...issueNodes); - /** - * BACKWARD COMPATIBILITY: Fallback to the search API if the issue was not found in the GraphQL response. - * This fallback will be removed in a future release - */ - if (issueNodes.length === 0) { - try { - const { - data: { items: backwardIssues }, - } = await octokit.request("GET /search/issues", { - q: `in:title+repo:${owner}/${repo}+type:issue+state:open+${title}`, - }); - issues.push(...backwardIssues); - } catch (error) { - logger.log( - "An error occured fetching issue via fallback (with GH SearchAPI)", - ); - } - } - const uniqueSRIssues = uniqBy( issues.filter((issue) => issue.body && issue.body.includes(ISSUE_ID)), "number", @@ -46,7 +27,7 @@ export default async (octokit, logger, title, labels, owner, repo) => { }; /** - * GraphQL Query to et the semantic-release issues for a repository. + * GraphQL Query to get the semantic-release issues for a repository. */ const loadGetSRIssuesQuery = `#graphql query getSRIssues($owner: String!, $repo: String!, $filter: IssueFilters) { diff --git a/lib/success.js b/lib/success.js index 5363863c..997368cf 100644 --- a/lib/success.js +++ b/lib/success.js @@ -266,14 +266,7 @@ export default async function success(pluginConfig, context, { Octokit }) { if (failComment === false || failTitle === false) { logger.log("Skip closing issue."); } else { - const srIssues = await findSRIssues( - octokit, - logger, - failTitle, - labels, - owner, - repo, - ); + const srIssues = await findSRIssues(octokit, logger, labels, owner, repo); debug("found semantic-release issues: %O", srIssues); diff --git a/package-lock.json b/package-lock.json index 57c0946f..a33136fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,7 +42,7 @@ "tempy": "3.1.0" }, "engines": { - "node": ">=20.8.1" + "node": "^22.14.0 || >= 24.3.0" }, "peerDependencies": { "semantic-release": ">=24.1.0" diff --git a/package.json b/package.json index 5252e996..6b9e7c55 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "tempy": "3.1.0" }, "engines": { - "node": ">=20.8.1" + "node": "^22.14.0 || >= 24.3.0" }, "files": [ "lib", diff --git a/test/fail.test.js b/test/fail.test.js index 6daa543d..6d1f9d91 100644 --- a/test/fail.test.js +++ b/test/fail.test.js @@ -48,16 +48,6 @@ test("Open a new issue with the list of errors", async (t) => { }, }, }) - .getOnce( - `https://api.github.local/search/issues?q=${encodeURIComponent( - "in:title", - )}+${encodeURIComponent( - `repo:${redirectedOwner}/${redirectedRepo}`, - )}+${encodeURIComponent("type:issue")}+${encodeURIComponent( - "state:open", - )}+${encodeURIComponent(failTitle)}`, - { items: [] }, - ) .postOnce( (url, { body }) => { t.is( @@ -136,14 +126,6 @@ test("Open a new issue with the list of errors and custom title and comment", as }, }, }) - .getOnce( - `https://api.github.local/search/issues?q=${encodeURIComponent( - "in:title", - )}+${encodeURIComponent(`repo:${owner}/${repo}`)}+${encodeURIComponent( - "type:issue", - )}+${encodeURIComponent("state:open")}+${encodeURIComponent(failTitle)}`, - { items: [] }, - ) .postOnce( `https://api.github.local/repos/${owner}/${repo}/issues`, { html_url: "https://github.com/issues/1", number: 1 }, @@ -211,14 +193,6 @@ test("Open a new issue with assignees and the list of errors", async (t) => { }, }, }) - .getOnce( - `https://api.github.local/search/issues?q=${encodeURIComponent( - "in:title", - )}+${encodeURIComponent(`repo:${owner}/${repo}`)}+${encodeURIComponent( - "type:issue", - )}+${encodeURIComponent("state:open")}+${encodeURIComponent(failTitle)}`, - { items: [] }, - ) .postOnce( (url, { body }) => { t.is(url, `https://api.github.local/repos/${owner}/${repo}/issues`); @@ -291,14 +265,6 @@ test("Open a new issue without labels and the list of errors", async (t) => { }, }, }) - .getOnce( - `https://api.github.local/search/issues?q=${encodeURIComponent( - "in:title", - )}+${encodeURIComponent(`repo:${owner}/${repo}`)}+${encodeURIComponent( - "type:issue", - )}+${encodeURIComponent("state:open")}+${encodeURIComponent(failTitle)}`, - { items: [] }, - ) .postOnce( (url, { body }) => { t.is(url, `https://api.github.local/repos/${owner}/${repo}/issues`); @@ -599,14 +565,6 @@ test(`Post new issue if none exists yet, but don't comment on existing issues wh }, }, ) - .getOnce( - `https://api.github.local/search/issues?q=${encodeURIComponent( - "in:title", - )}+${encodeURIComponent(`repo:${owner}/${repo}`)}+${encodeURIComponent( - "type:issue", - )}+${encodeURIComponent("state:open")}+${encodeURIComponent(failTitle)}`, - { items: [] }, - ) .postOnce( (url, { body }) => { t.is(url, `https://api.github.local/repos/${owner}/${repo}/issues`); diff --git a/test/find-sr-issue.test.js b/test/find-sr-issue.test.js index 3916e859..5009372d 100644 --- a/test/find-sr-issue.test.js +++ b/test/find-sr-issue.test.js @@ -69,7 +69,6 @@ test("Return empty array if not issues found", async (t) => { const repo = "test_repo"; const title = "The automated release is failing 🚨"; const labels = []; - const issues = []; const fetch = fetchMock .sandbox() .postOnce("https://api.github.local/graphql", { @@ -78,15 +77,7 @@ test("Return empty array if not issues found", async (t) => { issues: { nodes: [] }, }, }, - }) - .getOnce( - `https://api.github.local/search/issues?q=${encodeURIComponent( - "in:title", - )}+${encodeURIComponent(`repo:${owner}/${repo}`)}+${encodeURIComponent( - "type:issue", - )}+${encodeURIComponent("state:open")}+${encodeURIComponent(title)}`, - { items: issues }, - ); + }); const srIssues = await findSRIssues( new TestOctokit({ request: { fetch } }), @@ -133,84 +124,3 @@ test("Return empty array if not issues has matching ID", async (t) => { t.deepEqual(srIssues, []); t.true(fetch.done()); }); - -test("Handle error in searchAPI fallback", async (t) => { - const owner = "test_user"; - const repo = "test_repo"; - const title = "The automated release is failing 🚨"; - const labels = []; - const issues = []; - - const response = new Response("Not Found", { - url: "https://api.github.com/search/issues?q=in%3Atitle+repo%3Aourorg%2Frepo+type%3Aissue+state%3Aopen+The%20automated%20release%20is%20failing%20%F0%9F%9A%A8", - status: 403, - headers: { - "access-control-allow-origin": "*", - "access-control-expose-headers": - "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset", - "content-encoding": "gzip", - "content-security-policy": "default-repo 'none'", - "content-type": "application/json; charset=utf-8", - date: "Tue, 28 May 2024 19:49:00 GMT", - "referrer-policy": - "origin-when-cross-origin, strict-origin-when-cross-origin", - server: "GitHub.com", - "strict-transport-security": - "max-age=31536000; includeSubdomains; preload", - "transfer-encoding": "chunked", - vary: "Accept-Encoding, Accept, X-Requested-With", - "x-content-type-options": "nosniff", - "x-frame-options": "deny", - "x-github-api-version-selected": "2022-11-28", - "x-github-media-type": "github.v3; format=json", - "x-github-request-id": "2**0:29*****4:3868737:6*****3:6****52C", - "x-ratelimit-limit": "30", - "x-ratelimit-remaining": "30", - "x-ratelimit-reset": "1716925800", - "x-ratelimit-resource": "search", - "x-ratelimit-used": "1", - "x-xss-protection": "0", - }, - data: { - documentation_url: - "https://docs.github.com/free-pro-team@latest/rest/overview/rate-limits-for-the-rest-api#about-secondary-rate-limits", - message: - "You have exceeded a secondary rate limit. Please wait a few minutes before you try again. If you reach out to GitHub Support for help, please include the request ID 2840:295B44:3868737:64A2183:6232352C.", - }, - }); - - const fetch = fetchMock - .sandbox() - .postOnce("https://api.github.local/graphql", { - data: { - repository: { - issues: { nodes: issues }, - }, - }, - }) - .getOnce( - `https://api.github.local/search/issues?q=${encodeURIComponent( - "in:title", - )}+${encodeURIComponent(`repo:${owner}/${repo}`)}+${encodeURIComponent( - "type:issue", - )}+${encodeURIComponent("state:open")}+${encodeURIComponent(title)}`, - response, - ); - - const srIssues = await findSRIssues( - new TestOctokit({ request: { fetch } }), - t.context.logger, - title, - labels, - owner, - repo, - ); - - t.true( - t.context.log.calledWith( - "An error occured fetching issue via fallback (with GH SearchAPI)", - ), - ); - t.log(t.context.log); - t.true(fetch.done()); -}); diff --git a/test/integration.test.js b/test/integration.test.js index ffcae3ed..b4b83f55 100644 --- a/test/integration.test.js +++ b/test/integration.test.js @@ -517,14 +517,6 @@ test("Comment and add labels on PR included in the releases", async (t) => { }, }, }, - ) - .getOnce( - `https://api.github.local/search/issues?q=${encodeURIComponent( - "in:title", - )}+${encodeURIComponent(`repo:${owner}/${repo}`)}+${encodeURIComponent( - "type:issue", - )}+${encodeURIComponent("state:open")}+${encodeURIComponent(failTitle)}`, - { items: [] }, ); await t.context.m.success( @@ -590,14 +582,6 @@ test("Open a new issue with the list of errors", async (t) => { }, }, }) - .getOnce( - `https://api.github.local/search/issues?q=${encodeURIComponent( - "in:title", - )}+${encodeURIComponent(`repo:${owner}/${repo}`)}+${encodeURIComponent( - "type:issue", - )}+${encodeURIComponent("state:open")}+${encodeURIComponent(failTitle)}`, - { items: [] }, - ) .postOnce( (url, { body }) => { t.is(url, `https://api.github.local/repos/${owner}/${repo}/issues`); @@ -751,14 +735,6 @@ test("Verify, release and notify success", async (t) => { {}, { body: ["released"] }, ) - .getOnce( - `https://api.github.local/search/issues?q=${encodeURIComponent( - "in:title", - )}+${encodeURIComponent(`repo:${owner}/${repo}`)}+${encodeURIComponent( - "type:issue", - )}+${encodeURIComponent("state:open")}+${encodeURIComponent(failTitle)}`, - { items: [] }, - ) .postOnce( `${uploadOrigin}${uploadUri}?name=${encodeURIComponent("upload.txt")}&`, { browser_download_url: assetUrl }, @@ -938,14 +914,6 @@ test("Verify, update release and notify success", async (t) => { }, }, }, - ) - .getOnce( - `https://api.github.local/search/issues?q=${encodeURIComponent( - "in:title", - )}+${encodeURIComponent(`repo:${owner}/${repo}`)}+${encodeURIComponent( - "type:issue", - )}+${encodeURIComponent("state:open")}+${encodeURIComponent(failTitle)}`, - { items: [] }, ); await t.notThrowsAsync( @@ -1036,14 +1004,6 @@ test("Verify and notify failure", async (t) => { }, }, }) - .getOnce( - `https://api.github.local/search/issues?q=${encodeURIComponent( - "in:title", - )}+${encodeURIComponent(`repo:${owner}/${repo}`)}+${encodeURIComponent( - "type:issue", - )}+${encodeURIComponent("state:open")}+${encodeURIComponent(failTitle)}`, - { items: [] }, - ) .postOnce(`https://api.github.local/repos/${owner}/${repo}/issues`, { html_url: "https://github.com/issues/1", number: 1, diff --git a/test/success.test.js b/test/success.test.js index 6f8a4ed5..ef2083c0 100644 --- a/test/success.test.js +++ b/test/success.test.js @@ -239,16 +239,6 @@ test("Add comment and labels to PRs associated with release commits and issues s }, }, }, - ) - .getOnce( - `https://api.github.local/search/issues?q=${encodeURIComponent( - "in:title", - )}+${encodeURIComponent( - `repo:${redirectedOwner}/${redirectedRepo}`, - )}+${encodeURIComponent("type:issue")}+${encodeURIComponent( - "state:open", - )}+${encodeURIComponent(failTitle)}`, - { items: [] }, ); await success( @@ -537,16 +527,6 @@ test("Add comment and labels to PRs associated with release commits and issues ( }, }, }, - ) - .getOnce( - `https://api.github.local/search/issues?q=${encodeURIComponent( - "in:title", - )}+${encodeURIComponent( - `repo:${owner}/${repo}`, - )}+${encodeURIComponent("type:issue")}+${encodeURIComponent( - "state:open", - )}+${encodeURIComponent(failTitle)}`, - { items: [] }, ); await success( @@ -817,14 +797,6 @@ test("Add comment and labels to PRs associated with release commits and issues c }, }, }, - ) - .getOnce( - `https://custom-url.com/prefix/search/issues?q=${encodeURIComponent( - "in:title", - )}+${encodeURIComponent(`repo:${owner}/${repo}`)}+${encodeURIComponent( - "type:issue", - )}+${encodeURIComponent("state:open")}+${encodeURIComponent(failTitle)}`, - { items: [] }, ); await success( @@ -1118,14 +1090,6 @@ test("Make multiple search queries if necessary", async (t) => { }, }, }, - ) - .getOnce( - `https://api.github.local/search/issues?q=${encodeURIComponent( - "in:title", - )}+${encodeURIComponent(`repo:${owner}/${repo}`)}+${encodeURIComponent( - "type:issue", - )}+${encodeURIComponent("state:open")}+${encodeURIComponent(failTitle)}`, - { items: [] }, ); await success( @@ -1309,14 +1273,6 @@ test("Do not add comment and labels for unrelated PR returned by search (compare }, }, }, - ) - .getOnce( - `https://api.github.local/search/issues?q=${encodeURIComponent( - "in:title", - )}+${encodeURIComponent(`repo:${owner}/${repo}`)}+${encodeURIComponent( - "type:issue", - )}+${encodeURIComponent("state:open")}+${encodeURIComponent(failTitle)}`, - { items: [] }, ); await success( @@ -1408,14 +1364,6 @@ test("Do not add comment and labels if no PR is associated with release commits" }, }, }, - ) - .getOnce( - `https://api.github.local/search/issues?q=${encodeURIComponent( - "in:title", - )}+${encodeURIComponent(`repo:${owner}/${repo}`)}+${encodeURIComponent( - "type:issue", - )}+${encodeURIComponent("state:open")}+${encodeURIComponent(failTitle)}`, - { items: [] }, ); await success( @@ -1467,15 +1415,7 @@ test("Do not add comment and labels if no commits is found for release", async ( issues: { nodes: [] }, }, }, - }) - .getOnce( - `https://api.github.local/search/issues?q=${encodeURIComponent( - "in:title", - )}+${encodeURIComponent(`repo:${owner}/${repo}`)}+${encodeURIComponent( - "type:issue", - )}+${encodeURIComponent("state:open")}+${encodeURIComponent(failTitle)}`, - { items: [] }, - ); + }); await success( pluginConfig, @@ -1635,14 +1575,6 @@ test("Do not add comment and labels to PR/issues from other repo", async (t) => }, }, }, - ) - .getOnce( - `https://api.github.local/search/issues?q=${encodeURIComponent( - "in:title", - )}+${encodeURIComponent(`repo:${owner}/${repo}`)}+${encodeURIComponent( - "type:issue", - )}+${encodeURIComponent("state:open")}+${encodeURIComponent(failTitle)}`, - { items: [] }, ); await success( @@ -1930,14 +1862,6 @@ test("Ignore missing and forbidden issues/PRs", async (t) => { }, }, }, - ) - .getOnce( - `https://api.github.local/search/issues?q=${encodeURIComponent( - "in:title", - )}+${encodeURIComponent(`repo:${owner}/${repo}`)}+${encodeURIComponent( - "type:issue", - )}+${encodeURIComponent("state:open")}+${encodeURIComponent(failTitle)}`, - { items: [] }, ); await success( @@ -2085,14 +2009,6 @@ test("Add custom comment and labels", async (t) => { }, }, }, - ) - .getOnce( - `https://api.github.local/search/issues?q=${encodeURIComponent( - "in:title", - )}+${encodeURIComponent(`repo:${owner}/${repo}`)}+${encodeURIComponent( - "type:issue", - )}+${encodeURIComponent("state:open")}+${encodeURIComponent(failTitle)}`, - { items: [] }, ); await success( @@ -2202,14 +2118,6 @@ test("Add custom label", async (t) => { }, }, }, - ) - .getOnce( - `https://api.github.local/search/issues?q=${encodeURIComponent( - "in:title", - )}+${encodeURIComponent(`repo:${owner}/${repo}`)}+${encodeURIComponent( - "type:issue", - )}+${encodeURIComponent("state:open")}+${encodeURIComponent(failTitle)}`, - { items: [] }, ); await success( @@ -2310,14 +2218,6 @@ test("Comment on issue/PR without ading a label", async (t) => { }, }, }, - ) - .getOnce( - `https://api.github.local/search/issues?q=${encodeURIComponent( - "in:title", - )}+${encodeURIComponent(`repo:${owner}/${repo}`)}+${encodeURIComponent( - "type:issue", - )}+${encodeURIComponent("state:open")}+${encodeURIComponent(failTitle)}`, - { items: [] }, ); await success( @@ -2431,14 +2331,6 @@ test("Editing the release to include all release links at the bottom", async (t) }, }, ) - .getOnce( - `https://api.github.local/search/issues?q=${encodeURIComponent( - "in:title", - )}+${encodeURIComponent(`repo:${owner}/${repo}`)}+${encodeURIComponent( - "type:issue", - )}+${encodeURIComponent("state:open")}+${encodeURIComponent(failTitle)}`, - { items: [] }, - ) .patchOnce( `https://api.github.local/repos/${owner}/${repo}/releases/${releaseId}`, { @@ -2562,14 +2454,6 @@ test("Editing the release to include all release links at the top", async (t) => }, }, ) - .getOnce( - `https://api.github.local/search/issues?q=${encodeURIComponent( - "in:title", - )}+${encodeURIComponent(`repo:${owner}/${repo}`)}+${encodeURIComponent( - "type:issue", - )}+${encodeURIComponent("state:open")}+${encodeURIComponent(failTitle)}`, - { items: [] }, - ) .patchOnce( `https://api.github.local/repos/${owner}/${repo}/releases/${releaseId}`, { @@ -2689,14 +2573,6 @@ test("Editing the release to include all release links with no additional releas }, }, }, - ) - .getOnce( - `https://api.github.local/search/issues?q=${encodeURIComponent( - "in:title", - )}+${encodeURIComponent(`repo:${owner}/${repo}`)}+${encodeURIComponent( - "type:issue", - )}+${encodeURIComponent("state:open")}+${encodeURIComponent(failTitle)}`, - { items: [] }, ); await success( @@ -2806,14 +2682,6 @@ test("Editing the release to include all release links with no additional releas }, }, }, - ) - .getOnce( - `https://api.github.local/search/issues?q=${encodeURIComponent( - "in:title", - )}+${encodeURIComponent(`repo:${owner}/${repo}`)}+${encodeURIComponent( - "type:issue", - )}+${encodeURIComponent("state:open")}+${encodeURIComponent(failTitle)}`, - { items: [] }, ); await success( @@ -2916,14 +2784,6 @@ test("Editing the release to include all release links with no releases", async }, }, }, - ) - .getOnce( - `https://api.github.local/search/issues?q=${encodeURIComponent( - "in:title", - )}+${encodeURIComponent(`repo:${owner}/${repo}`)}+${encodeURIComponent( - "type:issue", - )}+${encodeURIComponent("state:open")}+${encodeURIComponent(failTitle)}`, - { items: [] }, ); await success( @@ -3028,14 +2888,6 @@ test("Editing the release with no ID in the release", async (t) => { }, }, }, - ) - .getOnce( - `https://api.github.local/search/issues?q=${encodeURIComponent( - "in:title", - )}+${encodeURIComponent(`repo:${owner}/${repo}`)}+${encodeURIComponent( - "type:issue", - )}+${encodeURIComponent("state:open")}+${encodeURIComponent(failTitle)}`, - { items: [] }, ); await success( @@ -3164,19 +3016,11 @@ test("Ignore errors when adding comments and closing issues", async (t) => { { data: { repository: { - issues: { nodes: [] }, + issues: { nodes: issues }, }, }, }, ) - .getOnce( - `https://api.github.local/search/issues?q=${encodeURIComponent( - "in:title", - )}+${encodeURIComponent(`repo:${owner}/${repo}`)}+${encodeURIComponent( - "type:issue", - )}+${encodeURIComponent("state:open")}+${encodeURIComponent(failTitle)}`, - { items: issues }, - ) .patchOnce( `https://api.github.local/repos/${owner}/${repo}/issues/2`, 500, @@ -3401,14 +3245,6 @@ test('Skip comment on on issues/PR if "successComment" is "false"', async (t) => }, }, }, - ) - .getOnce( - `https://api.github.local/search/issues?q=${encodeURIComponent( - "in:title", - )}+${encodeURIComponent(`repo:${owner}/${repo}`)}+${encodeURIComponent( - "type:issue", - )}+${encodeURIComponent("state:open")}+${encodeURIComponent(failTitle)}`, - { items: [] }, ); await success( @@ -3468,15 +3304,7 @@ test('Does not comment/label on issues/PR if "successCommentCondition" is "false issues: { nodes: [] }, }, }, - }) - .getOnce( - `https://api.github.local/search/issues?q=${encodeURIComponent( - "in:title", - )}+${encodeURIComponent(`repo:${owner}/${repo}`)}+${encodeURIComponent( - "type:issue", - )}+${encodeURIComponent("state:open")}+${encodeURIComponent(failTitle)}`, - { items: [] }, - ); + }); await success( pluginConfig, @@ -3687,19 +3515,11 @@ test('Add comment and label to found issues/associatedPR using the "successComme { data: { repository: { - issues: { nodes: [] }, + issues: { nodes: issues }, }, }, }, ) - .getOnce( - `https://api.github.local/search/issues?q=${encodeURIComponent( - "in:title", - )}+${encodeURIComponent(`repo:${owner}/${repo}`)}+${encodeURIComponent( - "type:issue", - )}+${encodeURIComponent("state:open")}+${encodeURIComponent(failTitle)}`, - { items: issues }, - ) .postOnce( `https://api.github.local/repos/${owner}/${repo}/issues/5/comments`, { html_url: "https://github.com/successcomment-5" }, @@ -4041,19 +3861,11 @@ test('Does not comment/label associatedPR and relatedIssues created by "Bots"', { data: { repository: { - issues: { nodes: [] }, + issues: { nodes: issues }, }, }, }, ) - .getOnce( - `https://api.github.local/search/issues?q=${encodeURIComponent( - "in:title", - )}+${encodeURIComponent(`repo:${owner}/${repo}`)}+${encodeURIComponent( - "type:issue", - )}+${encodeURIComponent("state:open")}+${encodeURIComponent(failTitle)}`, - { items: issues }, - ) .patchOnce( `https://api.github.local/repos/${owner}/${repo}/issues/1`, { html_url: "https://github.com/issues/1" }, pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy