From b5a72586159f0bc46f3014e6064c55103db3a401 Mon Sep 17 00:00:00 2001 From: Andreas Date: Wed, 9 Oct 2024 14:48:02 +0300 Subject: [PATCH] feat: Add `/suggestSource` endpoint for external use (#300) --- .env.local.example | 6 +- package.json | 2 + src/pages/api/github/fork.ts | 44 +++---- src/pages/api/github/newBranch.ts | 56 ++++----- src/pages/api/github/pr.ts | 55 +++----- src/pages/api/github/save.ts | 74 +++++------ src/pages/api/github/suggestSource.ts | 88 +++++++++++++ src/services/api/github/useGithub.ts | 66 ++-------- src/utils/getOctokit.ts | 79 ++++++++++++ src/utils/github.ts | 2 +- src/utils/githubApiErrorHandler.ts | 32 +++++ src/utils/index.ts | 2 +- yarn.lock | 174 ++++++++++++++++++++++++++ 13 files changed, 475 insertions(+), 205 deletions(-) create mode 100644 src/pages/api/github/suggestSource.ts create mode 100644 src/utils/getOctokit.ts create mode 100644 src/utils/githubApiErrorHandler.ts diff --git a/.env.local.example b/.env.local.example index 0b30a3ed..2707d66e 100644 --- a/.env.local.example +++ b/.env.local.example @@ -3,4 +3,8 @@ GITHUB_SECRET = "" NEXT_PUBLIC_APP_QUEUE_BASE_URL = "" NEXTAUTH_URL = "" NEXTAUTH_SECRET = "" -NEXT_PUBLIC_VERCEL_ENV = "" # development | production \ No newline at end of file +NEXT_PUBLIC_VERCEL_ENV = "" # development | production +# For 'Suggest a Source for Transcription' GitHub App +GITHUB_CURATOR_APP_ID = "" +GITHUB_CURATOR_PRIVATE_KEY_BASE64 = "" +GITHUB_CURATOR_INSTALLATION_ID = "" \ No newline at end of file diff --git a/package.json b/package.json index 1f67c89b..40e32304 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,9 @@ "@chakra-ui/react": "^2.8.0", "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", + "@octokit/auth-app": "^7.1.1", "@octokit/core": "^5.0.0", + "@octokit/rest": "^21.0.2", "@splidejs/react-splide": "^0.7.12", "@splidejs/splide": "^4.1.4", "@tanstack/react-query": "^4.32.6", diff --git a/src/pages/api/github/fork.ts b/src/pages/api/github/fork.ts index e5633cb8..abe5dac0 100644 --- a/src/pages/api/github/fork.ts +++ b/src/pages/api/github/fork.ts @@ -1,33 +1,21 @@ import type { NextApiRequest, NextApiResponse } from "next"; -import { Octokit } from "@octokit/core"; -import { auth } from "../auth/[...nextauth]"; +import { getOctokit } from "@/utils/getOctokit"; +import { withGithubErrorHandler } from "@/utils/githubApiErrorHandler"; -export default async function handler( - req: NextApiRequest, - res: NextApiResponse -) { - // Check if the user is authenticated - const session = await auth(req, res); - if (!session || !session.accessToken || !session.user?.githubUsername) { - return res.status(401).json({ message: "Unauthorized" }); - } +async function handler(req: NextApiRequest, res: NextApiResponse) { + const { octokit, owner } = await getOctokit(req, res); + const { repo } = req.body; - const { owner, repo } = req.body; - - // Initialize Octokit with the user's access token - const octokit = new Octokit({ auth: session.accessToken }); - - try { - // Fork the repository - const result = await octokit.request("POST /repos/{owner}/{repo}/forks", { - owner, - repo, - }); - res.status(200).json(result.data); - } catch (error) { - console.error("fork failed"); - console.error(error); - res.status(500).json({ message: "Error occurred while creating fork" }); - } + // Fork the repository + const result = await octokit.request("POST /repos/{owner}/{repo}/forks", { + owner, + repo, + }); + res.status(200).json(result.data); } + +export default withGithubErrorHandler( + handler, + "Error occurred while creating fork" +); diff --git a/src/pages/api/github/newBranch.ts b/src/pages/api/github/newBranch.ts index dadec107..d9719739 100644 --- a/src/pages/api/github/newBranch.ts +++ b/src/pages/api/github/newBranch.ts @@ -1,10 +1,11 @@ import { upstreamOwner } from "@/config/default"; -import { Octokit } from "@octokit/core"; +import { Octokit } from "@octokit/rest"; import { NextApiRequest, NextApiResponse } from "next"; -import { auth } from "../auth/[...nextauth]"; +import { getOctokit } from "@/utils/getOctokit"; +import { withGithubErrorHandler } from "@/utils/githubApiErrorHandler"; type NewBranchArgs = { - octokit: InstanceType; + octokit: Octokit; upstreamRepo: string; baseBranch: string; branchName: string; @@ -50,38 +51,25 @@ export async function createNewBranch({ }); } -export default async function handler( - req: NextApiRequest, - res: NextApiResponse -) { - // Check if the user is authenticated - const session = await auth(req, res); - if (!session || !session.accessToken || !session.user?.githubUsername) { - return res.status(401).json({ message: "Unauthorized" }); - } - +async function handler(req: NextApiRequest, res: NextApiResponse) { + const { octokit, owner } = await getOctokit(req, res); const { upstreamRepo, baseBranch, branchName } = req.body; - // Initialize Octokit with the user's access token - const octokit = new Octokit({ auth: session.accessToken }); - - try { - // Call the createNewBranch function - const result = await createNewBranch({ - octokit, - upstreamRepo, - baseBranch, - branchName, - owner: session.user.githubUsername, - }); + const result = await createNewBranch({ + octokit, + upstreamRepo, + baseBranch, + branchName, + owner, + }); - res.status(200).json({ - message: "succesfully created a new branch", - ...result, - }); - } catch (error: any) { - res.status(500).json({ - message: error?.message ?? "Error occurred while creating new branch", - }); - } + res.status(200).json({ + message: "successfully created a new branch", + ...result, + }); } + +export default withGithubErrorHandler( + handler, + "Error occurred while creating new branch" +); diff --git a/src/pages/api/github/pr.ts b/src/pages/api/github/pr.ts index cd0b6070..fc1f3995 100644 --- a/src/pages/api/github/pr.ts +++ b/src/pages/api/github/pr.ts @@ -1,41 +1,26 @@ import type { NextApiRequest, NextApiResponse } from "next"; -import { Octokit } from "@octokit/core"; - import { createPullRequest } from "@/utils/github"; -import { auth } from "../auth/[...nextauth]"; - -export default async function handler( - req: NextApiRequest, - res: NextApiResponse -) { - // Check if the user is authenticated - const session = await auth(req, res); - if (!session || !session.accessToken || !session.user?.githubUsername) { - return res.status(401).json({ message: "Unauthorized" }); - } +import { getOctokit } from "@/utils/getOctokit"; +import { withGithubErrorHandler } from "@/utils/githubApiErrorHandler"; - // Initialize Octokit with the user's access token - const octokit = new Octokit({ auth: session.accessToken }); +async function handler(req: NextApiRequest, res: NextApiResponse) { + const { octokit, owner } = await getOctokit(req, res); + const { repo, title, body, head, base } = req.body; - const { owner, repo, title, body, head, base } = req.body; + const prResult = await createPullRequest({ + octokit, + owner, + repo, + title, + body, + head, + base, + }); - try { - const prResult = await createPullRequest({ - octokit, - owner, - repo, - title, - body, - head, - base, - }); - - return res.status(200).json(prResult.data); - } catch (error: any) { - console.error(error); - res.status(500).json({ - message: - error?.message ?? "Error occurred while creating the Pull Request", - }); - } + return res.status(200).json(prResult.data); } + +export default withGithubErrorHandler( + handler, + "Error occurred while creating the Pull Request" +); diff --git a/src/pages/api/github/save.ts b/src/pages/api/github/save.ts index 43724835..c991cea4 100644 --- a/src/pages/api/github/save.ts +++ b/src/pages/api/github/save.ts @@ -1,8 +1,8 @@ import type { NextApiRequest, NextApiResponse } from "next"; -import { Octokit } from "@octokit/core"; import { getFileSha, updateOrCreateFile } from "@/utils/github"; -import { auth } from "../auth/[...nextauth]"; +import { getOctokit } from "@/utils/getOctokit"; +import { withGithubErrorHandler } from "@/utils/githubApiErrorHandler"; export const config = { api: { @@ -14,51 +14,33 @@ export const config = { }, }; -export default async function handler( - req: NextApiRequest, - res: NextApiResponse -) { - // Check if the user is authenticated - const session = await auth(req, res); - if ( - !session || - !session.accessToken || - !session.user?.jwt || - !session.user?.githubUsername - ) { - return res.status(401).json({ message: "Unauthorized" }); - } - - // Initialize Octokit with the user's access token - const octokit = new Octokit({ auth: session.accessToken }); +async function handler(req: NextApiRequest, res: NextApiResponse) { + const { octokit, owner } = await getOctokit(req, res); const { repo, filePath, fileContent, branch } = req.body; - try { - const fileSha = await getFileSha({ - octokit, - owner: session.user.githubUsername, - repo, - path: filePath, - branch, - }); - - await updateOrCreateFile({ - octokit, - owner: session.user.githubUsername, - repo, - path: filePath, - fileContent, - branch, - sha: fileSha, - }); - - res.status(200).json({ message: "Successfully saved edits" }); - } catch (error: any) { - console.error(error); - const errMessage = error?.message; - res - .status(500) - .json({ message: errMessage ?? "Error occurred while saving fork" }); - } + const fileSha = await getFileSha({ + octokit, + owner, + repo, + path: filePath, + branch, + }); + + await updateOrCreateFile({ + octokit, + owner, + repo, + path: filePath, + fileContent, + branch, + sha: fileSha, + }); + + res.status(200).json({ message: "Successfully saved edits" }); } + +export default withGithubErrorHandler( + handler, + "Error occurred while creating the Pull Request" +); diff --git a/src/pages/api/github/suggestSource.ts b/src/pages/api/github/suggestSource.ts new file mode 100644 index 00000000..5c7e5b0e --- /dev/null +++ b/src/pages/api/github/suggestSource.ts @@ -0,0 +1,88 @@ +import type { NextApiRequest, NextApiResponse } from "next"; +import yaml from "js-yaml"; +import { getOctokit } from "@/utils/getOctokit"; +import { upstreamOwner, upstreamRepo } from "@/config/default"; +import { deriveFileSlug } from "@/utils"; +import { createNewBranch } from "./newBranch"; +import { withGithubErrorHandler } from "@/utils/githubApiErrorHandler"; + +async function handler(req: NextApiRequest, res: NextApiResponse) { + if (req.method !== "POST") { + return res.status(405).json({ message: "Method not allowed" }); + } + + const { octokit, owner } = await getOctokit(req, res, { + allowAppFallback: true, + }); + const { title, media, targetRepository } = req.body; + + if (owner !== upstreamOwner) { + // Fork the main repository + await octokit.request("POST /repos/{owner}/{repo}/forks", { + owner: upstreamOwner, + repo: upstreamRepo, + }); + } + + // Create new branch + const timeInSeconds = Math.floor(Date.now() / 1000); + const fileName = deriveFileSlug(title); + const branchName = `${timeInSeconds}-${fileName}`; + await createNewBranch({ + octokit, + upstreamRepo, + baseBranch: + process.env.NEXT_PUBLIC_VERCEL_ENV === "production" + ? "master" + : "staging", + branchName, + owner, + }); + + // Save file + const transcriptMarkdown = + `---\n` + + yaml.dump( + { + title, + media, + needs: "transcript", + }, + { + forceQuotes: true, + } + ) + + "---\n"; + + await octokit.request("PUT /repos/{owner}/{repo}/contents/{path}", { + owner, + repo: upstreamRepo, + path: `misc/${fileName}.md`, + message: `curate(transcript): "${title}"`, + content: Buffer.from(transcriptMarkdown).toString("base64"), + branch: branchName, + }); + + // Open PR with user's suggestion + const prResult = await octokit.request("POST /repos/{owner}/{repo}/pulls", { + owner: targetRepository === "user" ? owner : upstreamOwner, + repo: upstreamRepo, + title: `curate(transcript): "${title}"`, + body: `This PR is a suggestion for the transcription of [${title}](${media}).`, + head: `${owner}:${branchName}`, + base: + process.env.NEXT_PUBLIC_VERCEL_ENV === "production" + ? "master" + : "staging", + }); + + res.status(200).json({ + message: "Suggestion submitted successfully", + pr_url: prResult.data.html_url, + }); +} + +export default withGithubErrorHandler( + handler, + "Error occurred while submitting suggestion" +); diff --git a/src/services/api/github/useGithub.ts b/src/services/api/github/useGithub.ts index 4313412e..845d6f05 100644 --- a/src/services/api/github/useGithub.ts +++ b/src/services/api/github/useGithub.ts @@ -19,7 +19,6 @@ import { } from "@/utils/github"; import { useUserMultipleReviews } from "@/services/api/reviews"; import config from "@/config/config.json"; -import { deriveFileSlug } from "@/utils"; import backendAxios from "../axios"; import { Review, TranscriptMetadata } from "../../../../types"; @@ -203,64 +202,12 @@ const suggestSource = async ({ media, targetRepository, }: SuggestSourceParams) => { - // Fork repository - const forkMainRepoResult = await githubApi.post("/fork", { - owner: upstreamOwner, - repo: upstreamRepo, - }); - const owner = forkMainRepoResult.data.owner.login; - - // Create a new branch - const timeInSeconds = Math.floor(Date.now() / 1000); - const fileName = deriveFileSlug(title); - const branchName = `${timeInSeconds}-${fileName}`; - await githubApi.post("/newBranch", { - upstreamRepo, - // TODO: needs better handling of base branch - baseBranch: - process.env.NEXT_PUBLIC_VERCEL_ENV === "production" - ? "master" - : "staging", - branchName, - }); - - // Save file - const transcriptMarkdown = - `---\n` + - yaml.dump( - { - title, - media, - needs: "transcript", - }, - { - forceQuotes: true, - } - ) + - "---\n"; - await githubApi.post("/save", { - repo: upstreamRepo, - // for now misc is the default directory for suggestions - filePath: `misc/${fileName}.md`, - fileContent: transcriptMarkdown, - branch: branchName, - }); - - // Open PR with user's suggestion - const prResult = await githubApi.post("/pr", { - owner: targetRepository === "user" ? owner : upstreamOwner, - // we don't expect that the user will have a different name for their fork - repo: upstreamRepo, - title: `suggest: "${title}"`, - body: `This PR is a suggestion for the transcription of [${title}](${media}).`, - head: `${owner}:${branchName}`, - // TODO: needs better handling of base branch - base: - process.env.NEXT_PUBLIC_VERCEL_ENV === "production" - ? "master" - : "staging", + const result = await githubApi.post("/suggestSource", { + title, + media, + targetRepository, }); - return prResult.data.html_url; + return result.data.pr_url; }; export function useGithub() { @@ -313,10 +260,11 @@ export function useGithub() { }); const mutationSuggestSource = useMutation(suggestSource, { - onSuccess: () => { + onSuccess: (pr_url) => { toast({ status: "success", title: "Suggestion submitted successfully", + description: `Find your suggestion at ${pr_url}`, }); }, onError: (e) => { diff --git a/src/utils/getOctokit.ts b/src/utils/getOctokit.ts new file mode 100644 index 00000000..d5e78547 --- /dev/null +++ b/src/utils/getOctokit.ts @@ -0,0 +1,79 @@ +import { NextApiRequest, NextApiResponse } from "next"; +import { Octokit } from "@octokit/rest"; +import { createAppAuth } from "@octokit/auth-app"; +import { auth } from "../pages/api/auth/[...nextauth]"; +import { UnauthorizedError } from "./githubApiErrorHandler"; + +type GetOctokitOptions = { + allowAppFallback?: boolean; +}; + +export async function getOctokit( + req: NextApiRequest, + res: NextApiResponse, + options: GetOctokitOptions = {} +) { + let octokit: Octokit; + let owner: string | undefined; + + // Try session-based authentication first + const session = await auth(req, res); + if (session?.accessToken) { + octokit = new Octokit({ auth: session.accessToken }); + owner = session.user?.githubUsername; + } + + // Fall back to GitHub App if no session + else { + // If no session and app fallback is not allowed, send unauthorized response + if (!options.allowAppFallback) { + throw new UnauthorizedError("Unauthorized"); + } + + const privateKeyBase64 = process.env.GITHUB_CURATOR_PRIVATE_KEY_BASE64; + + if (!privateKeyBase64) { + throw new Error( + "GITHUB_CURATOR_PRIVATE_KEY environment variable is not set" + ); + } + + // Decode the base64 private key + const privateKey = Buffer.from(privateKeyBase64, "base64").toString("utf8"); + + const appOctokit = new Octokit({ + authStrategy: createAppAuth, + auth: { + appId: process.env.GITHUB_CURATOR_APP_ID, + privateKey: privateKey, + installationId: process.env.GITHUB_CURATOR_INSTALLATION_ID, + }, + }); + + // List all installations to find the correct one + const installations = await appOctokit.apps.listInstallations(); + const installation = installations.data.find( + (inst) => + inst.id.toString() === process.env.GITHUB_CURATOR_INSTALLATION_ID + ); + + if (!installation) { + throw new Error("Installation not found"); + } + owner = installation.account?.login; + + const { + data: { token }, + } = await appOctokit.apps.createInstallationAccessToken({ + installation_id: Number(process.env.GITHUB_CURATOR_INSTALLATION_ID), + }); + + octokit = new Octokit({ auth: token }); + } + + if (!owner) { + throw new Error("Unable to determine owner"); + } + + return { octokit, owner }; +} diff --git a/src/utils/github.ts b/src/utils/github.ts index f3e56320..dd25ddb0 100644 --- a/src/utils/github.ts +++ b/src/utils/github.ts @@ -1,5 +1,5 @@ import { AxiosError } from "axios"; -import { Octokit } from "@octokit/core"; +import { Octokit } from "@octokit/rest"; import { upstreamMetadataRepo } from "@/config/default"; diff --git a/src/utils/githubApiErrorHandler.ts b/src/utils/githubApiErrorHandler.ts new file mode 100644 index 00000000..a6616077 --- /dev/null +++ b/src/utils/githubApiErrorHandler.ts @@ -0,0 +1,32 @@ +import { NextApiRequest, NextApiResponse } from "next"; + +export class UnauthorizedError extends Error { + constructor(message: string) { + super(message); + this.name = "UnauthorizedError"; + } +} + +type ApiHandler = (req: NextApiRequest, res: NextApiResponse) => Promise; + +export function withGithubErrorHandler( + handler: ApiHandler, + customErrorMessage?: string +): ApiHandler { + return async (req: NextApiRequest, res: NextApiResponse) => { + try { + await handler(req, res); + } catch (error: any) { + if (error instanceof UnauthorizedError) { + return res.status(401).json({ message: "Unauthorized" }); + } + console.error(error); + res.status(500).json({ + message: + error?.message ?? + customErrorMessage ?? + "An unexpected error occurred", + }); + } + }; +} diff --git a/src/utils/index.ts b/src/utils/index.ts index b00cc2e3..316580fd 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -119,7 +119,7 @@ export function deriveFileSlug(title: string, regex?: RegExp) { const fileSlug = slugify(_trimmedFileName, { strict: false, lower: true, - remove: regex, + remove: regex || /[:'"]/g, // Removes colons, single quotes, and double quotes }); return fileSlug; } diff --git a/yarn.lock b/yarn.lock index 5012a218..adec7a94 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1199,11 +1199,62 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@octokit/auth-app@^7.1.1": + version "7.1.1" + resolved "https://registry.yarnpkg.com/@octokit/auth-app/-/auth-app-7.1.1.tgz#d8916ad01e6ffb0a0a50507aa613e91fe7a49b93" + integrity sha512-kRAd6yelV9OgvlEJE88H0VLlQdZcag9UlLr7dV0YYP37X8PPDvhgiTy66QVhDXdyoT0AleFN2w/qXkPdrSzINg== + dependencies: + "@octokit/auth-oauth-app" "^8.1.0" + "@octokit/auth-oauth-user" "^5.1.0" + "@octokit/request" "^9.1.1" + "@octokit/request-error" "^6.1.1" + "@octokit/types" "^13.4.1" + lru-cache "^10.0.0" + universal-github-app-jwt "^2.2.0" + universal-user-agent "^7.0.0" + +"@octokit/auth-oauth-app@^8.1.0": + version "8.1.1" + resolved "https://registry.yarnpkg.com/@octokit/auth-oauth-app/-/auth-oauth-app-8.1.1.tgz#6204affa6e86f535016799cadf2af9befe5e893c" + integrity sha512-5UtmxXAvU2wfcHIPPDWzVSAWXVJzG3NWsxb7zCFplCWEmMCArSZV0UQu5jw5goLQXbFyOr5onzEH37UJB3zQQg== + dependencies: + "@octokit/auth-oauth-device" "^7.0.0" + "@octokit/auth-oauth-user" "^5.0.1" + "@octokit/request" "^9.0.0" + "@octokit/types" "^13.0.0" + universal-user-agent "^7.0.0" + +"@octokit/auth-oauth-device@^7.0.0", "@octokit/auth-oauth-device@^7.0.1": + version "7.1.1" + resolved "https://registry.yarnpkg.com/@octokit/auth-oauth-device/-/auth-oauth-device-7.1.1.tgz#7b4f8f97cbcadbe9894d48cde4406dbdef39875a" + integrity sha512-HWl8lYueHonuyjrKKIup/1tiy0xcmQCdq5ikvMO1YwkNNkxb6DXfrPjrMYItNLyCP/o2H87WuijuE+SlBTT8eg== + dependencies: + "@octokit/oauth-methods" "^5.0.0" + "@octokit/request" "^9.0.0" + "@octokit/types" "^13.0.0" + universal-user-agent "^7.0.0" + +"@octokit/auth-oauth-user@^5.0.1", "@octokit/auth-oauth-user@^5.1.0": + version "5.1.1" + resolved "https://registry.yarnpkg.com/@octokit/auth-oauth-user/-/auth-oauth-user-5.1.1.tgz#4f1570c6ee15bb9ddc3dcca83308dcaa159e3848" + integrity sha512-rRkMz0ErOppdvEfnemHJXgZ9vTPhBuC6yASeFaB7I2yLMd7QpjfrL1mnvRPlyKo+M6eeLxrKanXJ9Qte29SRsw== + dependencies: + "@octokit/auth-oauth-device" "^7.0.1" + "@octokit/oauth-methods" "^5.0.0" + "@octokit/request" "^9.0.1" + "@octokit/types" "^13.0.0" + universal-user-agent "^7.0.0" + "@octokit/auth-token@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-4.0.0.tgz#40d203ea827b9f17f42a29c6afb93b7745ef80c7" integrity sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA== +"@octokit/auth-token@^5.0.0": + version "5.1.1" + resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-5.1.1.tgz#3bbfe905111332a17f72d80bd0b51a3e2fa2cf07" + integrity sha512-rh3G3wDO8J9wSjfI436JUKzHIxq8NaiL0tVeB2aXmG6p/9859aUOAjA9pmSPNGGZxfwmaJ9ozOJImuNVJdpvbA== + "@octokit/core@^5.0.0": version "5.0.0" resolved "https://registry.yarnpkg.com/@octokit/core/-/core-5.0.0.tgz#0fc2b6eb88437e5c1d69f756a5dcee7472d2b2dd" @@ -1217,6 +1268,27 @@ before-after-hook "^2.2.0" universal-user-agent "^6.0.0" +"@octokit/core@^6.1.2": + version "6.1.2" + resolved "https://registry.yarnpkg.com/@octokit/core/-/core-6.1.2.tgz#20442d0a97c411612da206411e356014d1d1bd17" + integrity sha512-hEb7Ma4cGJGEUNOAVmyfdB/3WirWMg5hDuNFVejGEDFqupeOysLc2sG6HJxY2etBp5YQu5Wtxwi020jS9xlUwg== + dependencies: + "@octokit/auth-token" "^5.0.0" + "@octokit/graphql" "^8.0.0" + "@octokit/request" "^9.0.0" + "@octokit/request-error" "^6.0.1" + "@octokit/types" "^13.0.0" + before-after-hook "^3.0.2" + universal-user-agent "^7.0.0" + +"@octokit/endpoint@^10.0.0": + version "10.1.1" + resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-10.1.1.tgz#1a9694e7aef6aa9d854dc78dd062945945869bcc" + integrity sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q== + dependencies: + "@octokit/types" "^13.0.0" + universal-user-agent "^7.0.2" + "@octokit/endpoint@^9.0.0": version "9.0.0" resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-9.0.0.tgz#c5ce19c74b999b85af9a8a189275c80faa3e90fd" @@ -1235,11 +1307,59 @@ "@octokit/types" "^11.0.0" universal-user-agent "^6.0.0" +"@octokit/graphql@^8.0.0": + version "8.1.1" + resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-8.1.1.tgz#3cacab5f2e55d91c733e3bf481d3a3f8a5f639c4" + integrity sha512-ukiRmuHTi6ebQx/HFRCXKbDlOh/7xEV6QUXaE7MJEKGNAncGI/STSbOkl12qVXZrfZdpXctx5O9X1AIaebiDBg== + dependencies: + "@octokit/request" "^9.0.0" + "@octokit/types" "^13.0.0" + universal-user-agent "^7.0.0" + +"@octokit/oauth-authorization-url@^7.0.0": + version "7.1.1" + resolved "https://registry.yarnpkg.com/@octokit/oauth-authorization-url/-/oauth-authorization-url-7.1.1.tgz#0e17c2225eb66b58ec902d02b6f1315ffe9ff04b" + integrity sha512-ooXV8GBSabSWyhLUowlMIVd9l1s2nsOGQdlP2SQ4LnkEsGXzeCvbSbCPdZThXhEFzleGPwbapT0Sb+YhXRyjCA== + +"@octokit/oauth-methods@^5.0.0": + version "5.1.2" + resolved "https://registry.yarnpkg.com/@octokit/oauth-methods/-/oauth-methods-5.1.2.tgz#fd31d2a69f4c91d1abc1ed1814dda5252c697e02" + integrity sha512-C5lglRD+sBlbrhCUTxgJAFjWgJlmTx5bQ7Ch0+2uqRjYv7Cfb5xpX4WuSC9UgQna3sqRGBL9EImX9PvTpMaQ7g== + dependencies: + "@octokit/oauth-authorization-url" "^7.0.0" + "@octokit/request" "^9.1.0" + "@octokit/request-error" "^6.1.0" + "@octokit/types" "^13.0.0" + "@octokit/openapi-types@^18.0.0": version "18.0.0" resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-18.0.0.tgz#f43d765b3c7533fd6fb88f3f25df079c24fccf69" integrity sha512-V8GImKs3TeQRxRtXFpG2wl19V7444NIOTDF24AWuIbmNaNYOQMWRbjcGDXV5B+0n887fgDcuMNOmlul+k+oJtw== +"@octokit/openapi-types@^22.2.0": + version "22.2.0" + resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-22.2.0.tgz#75aa7dcd440821d99def6a60b5f014207ae4968e" + integrity sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg== + +"@octokit/plugin-paginate-rest@^11.0.0": + version "11.3.3" + resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.3.3.tgz#efc97ba66aae6797e2807a082f99b9cfc0e05aba" + integrity sha512-o4WRoOJZlKqEEgj+i9CpcmnByvtzoUYC6I8PD2SA95M+BJ2x8h7oLcVOg9qcowWXBOdcTRsMZiwvM3EyLm9AfA== + dependencies: + "@octokit/types" "^13.5.0" + +"@octokit/plugin-request-log@^5.3.1": + version "5.3.1" + resolved "https://registry.yarnpkg.com/@octokit/plugin-request-log/-/plugin-request-log-5.3.1.tgz#ccb75d9705de769b2aa82bcd105cc96eb0c00f69" + integrity sha512-n/lNeCtq+9ofhC15xzmJCNKP2BWTv8Ih2TTy+jatNCCq/gQP/V7rK3fjIfuz0pDWDALO/o/4QY4hyOF6TQQFUw== + +"@octokit/plugin-rest-endpoint-methods@^13.0.0": + version "13.2.4" + resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-13.2.4.tgz#543add032d3fe3f5d2839bfd619cf66d85469f01" + integrity sha512-gusyAVgTrPiuXOdfqOySMDztQHv6928PQ3E4dqVGEtOvRXAKRbJR4b1zQyniIT9waqaWk/UDaoJ2dyPr7Bk7Iw== + dependencies: + "@octokit/types" "^13.5.0" + "@octokit/request-error@^5.0.0": version "5.0.0" resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-5.0.0.tgz#060c5770833f9d563ad9a49fec6650c41584bc40" @@ -1249,6 +1369,13 @@ deprecation "^2.0.0" once "^1.4.0" +"@octokit/request-error@^6.0.1", "@octokit/request-error@^6.1.0", "@octokit/request-error@^6.1.1": + version "6.1.4" + resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-6.1.4.tgz#ad96e29148d19edc2ba8009fc2b5a24a36c90f16" + integrity sha512-VpAhIUxwhWZQImo/dWAN/NpPqqojR6PSLgLYAituLM6U+ddx9hCioFGwBr5Mi+oi5CLeJkcAs3gJ0PYYzU6wUg== + dependencies: + "@octokit/types" "^13.0.0" + "@octokit/request@^8.0.1", "@octokit/request@^8.0.2": version "8.1.1" resolved "https://registry.yarnpkg.com/@octokit/request/-/request-8.1.1.tgz#23b4d3f164e973f4c1a0f24f68256f1646c00620" @@ -1260,6 +1387,26 @@ is-plain-object "^5.0.0" universal-user-agent "^6.0.0" +"@octokit/request@^9.0.0", "@octokit/request@^9.0.1", "@octokit/request@^9.1.0", "@octokit/request@^9.1.1": + version "9.1.3" + resolved "https://registry.yarnpkg.com/@octokit/request/-/request-9.1.3.tgz#42b693bc06238f43af3c037ebfd35621c6457838" + integrity sha512-V+TFhu5fdF3K58rs1pGUJIDH5RZLbZm5BI+MNF+6o/ssFNT4vWlCh/tVpF3NxGtP15HUxTTMUbsG5llAuU2CZA== + dependencies: + "@octokit/endpoint" "^10.0.0" + "@octokit/request-error" "^6.0.1" + "@octokit/types" "^13.1.0" + universal-user-agent "^7.0.2" + +"@octokit/rest@^21.0.2": + version "21.0.2" + resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-21.0.2.tgz#9b767dbc1098daea8310fd8b76bf7a97215d5972" + integrity sha512-+CiLisCoyWmYicH25y1cDfCrv41kRSvTq6pPWtRroRJzhsCZWZyCqGyI8foJT5LmScADSwRAnr/xo+eewL04wQ== + dependencies: + "@octokit/core" "^6.1.2" + "@octokit/plugin-paginate-rest" "^11.0.0" + "@octokit/plugin-request-log" "^5.3.1" + "@octokit/plugin-rest-endpoint-methods" "^13.0.0" + "@octokit/types@^11.0.0", "@octokit/types@^11.1.0": version "11.1.0" resolved "https://registry.yarnpkg.com/@octokit/types/-/types-11.1.0.tgz#9e5db741d582b05718a4d91bac8cc987def235ea" @@ -1267,6 +1414,13 @@ dependencies: "@octokit/openapi-types" "^18.0.0" +"@octokit/types@^13.0.0", "@octokit/types@^13.1.0", "@octokit/types@^13.4.1", "@octokit/types@^13.5.0": + version "13.5.0" + resolved "https://registry.yarnpkg.com/@octokit/types/-/types-13.5.0.tgz#4796e56b7b267ebc7c921dcec262b3d5bfb18883" + integrity sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ== + dependencies: + "@octokit/openapi-types" "^22.2.0" + "@panva/hkdf@^1.0.2": version "1.1.1" resolved "https://registry.yarnpkg.com/@panva/hkdf/-/hkdf-1.1.1.tgz#ab9cd8755d1976e72fc77a00f7655a64efe6cd5d" @@ -1751,6 +1905,11 @@ before-after-hook@^2.2.0: resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.3.tgz#c51e809c81a4e354084422b9b26bad88249c517c" integrity sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ== +before-after-hook@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-3.0.2.tgz#d5665a5fa8b62294a5aa0a499f933f4a1016195d" + integrity sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A== + big-integer@^1.6.44: version "1.6.51" resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686" @@ -3534,6 +3693,11 @@ loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: dependencies: js-tokens "^3.0.0 || ^4.0.0" +lru-cache@^10.0.0: + version "10.4.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" + integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== + lru-cache@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" @@ -4818,11 +4982,21 @@ undici-types@~6.13.0: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.13.0.tgz#e3e79220ab8c81ed1496b5812471afd7cf075ea5" integrity sha512-xtFJHudx8S2DSoujjMd1WeWvn7KKWFRESZTMeL1RptAYERu29D6jphMjjY+vn96jvN3kVPDNxU/E13VTaXj6jg== +universal-github-app-jwt@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/universal-github-app-jwt/-/universal-github-app-jwt-2.2.0.tgz#dc6c8929e76f1996a766ba2a08fb420f73365d77" + integrity sha512-G5o6f95b5BggDGuUfKDApKaCgNYy2x7OdHY0zSMF081O0EJobw+1130VONhrA7ezGSV2FNOGyM+KQpQZAr9bIQ== + universal-user-agent@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.0.tgz#3381f8503b251c0d9cd21bc1de939ec9df5480ee" integrity sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w== +universal-user-agent@^7.0.0, universal-user-agent@^7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-7.0.2.tgz#52e7d0e9b3dc4df06cc33cb2b9fd79041a54827e" + integrity sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q== + untildify@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b"