diff --git a/next.config.mjs b/next.config.mjs index b5bcefb..8c4eaec 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,9 +1,9 @@ /** @type {import('next').NextConfig} */ const nextConfig = { - webpack: (config) => { - config.externals.push("@node-rs/argon2"); - return config; - } + webpack: (config) => { + config.externals.push('@node-rs/argon2'); + return config; + }, }; export default nextConfig; diff --git a/package.json b/package.json index 7dd61b4..f7025c2 100644 --- a/package.json +++ b/package.json @@ -15,16 +15,20 @@ "@hookform/resolvers": "^3.9.1", "@lucia-auth/adapter-prisma": "^4.0.1", "@node-rs/argon2": "^2.0.2", + "@octokit/app": "^15.1.1", "@octokit/core": "^6.1.2", "@prisma/client": "^6.0.1", "@radix-ui/react-avatar": "^1.0.4", + "@radix-ui/react-dialog": "^1.1.4", "@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-label": "^2.1.1", + "@radix-ui/react-popover": "^1.1.4", "@radix-ui/react-slot": "^1.1.1", "@radix-ui/react-tabs": "^1.1.2", "arctic": "^2.3.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.0", + "cmdk": "1.0.0", "ioredis": "^5.4.1", "lucia": "^3.1.1", "lucide-react": "^0.368.0", diff --git a/prisma/migrations/20241220113628_add_installation/migration.sql b/prisma/migrations/20241220113628_add_installation/migration.sql new file mode 100644 index 0000000..d48200e --- /dev/null +++ b/prisma/migrations/20241220113628_add_installation/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "User" ADD COLUMN "installations" TEXT[]; diff --git a/prisma/migrations/20241220151510_optional_github/migration.sql b/prisma/migrations/20241220151510_optional_github/migration.sql new file mode 100644 index 0000000..eeec1e8 --- /dev/null +++ b/prisma/migrations/20241220151510_optional_github/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Project" ALTER COLUMN "github" DROP NOT NULL; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 9fded08..786a5d0 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -14,11 +14,12 @@ datasource db { } model User { - id String @id @default(cuid()) - githubId String @unique - username String - projects Project[] - sessions Session[] + id String @id @default(cuid()) + githubId String @unique + username String + installations String[] + projects Project[] + sessions Session[] } model Session { @@ -32,7 +33,7 @@ model Project { id String @id @default(cuid()) name String description String - github String + github String? customData String[] rateLimitReq Int @default(5) rateLimitTime Int @default(60) diff --git a/src/app/(protected)/create/page.tsx b/src/app/(protected)/create/page.tsx index df4e3de..cd1d484 100644 --- a/src/app/(protected)/create/page.tsx +++ b/src/app/(protected)/create/page.tsx @@ -26,14 +26,9 @@ export default function CreateForm() { placeholder: 'A developer-centric user feedback platform.', description: 'Describe the project a bit.', }, - { - name: 'github', - label: 'Github', - placeholder: 'https://github.com/SrIzan10/echospace', - }, ]} submitText="Submit" - submitClassname="w-full" + submitClassname="w-full !mt-5" onActionComplete={(res) => { // @ts-ignore yea if (res && res.id) { diff --git a/src/app/(public)/auth/github/callback/route.ts b/src/app/(public)/auth/github/callback/route.ts index 4b21d44..fcd7e4f 100644 --- a/src/app/(public)/auth/github/callback/route.ts +++ b/src/app/(public)/auth/github/callback/route.ts @@ -9,6 +9,15 @@ export async function GET(request: Request): Promise { const url = new URL(request.url); const code = url.searchParams.get('code'); const state = url.searchParams.get('state'); + const setupAction = url.searchParams.get('setup_action'); + if (setupAction === 'install') { + return new Response(null, { + status: 302, + headers: { + Location: '/', + }, + }); + } const storedState = (await cookies()).get('github_oauth_state')?.value ?? null; if (!code || !state || !storedState || state !== storedState) { return new Response(null, { @@ -66,6 +75,7 @@ export async function GET(request: Request): Promise { console.error(e); if (e instanceof OAuth2RequestError) { // invalid code + console.error(e); return new Response(null, { status: 400, }); diff --git a/src/app/(public)/auth/github/webhooks/route.ts b/src/app/(public)/auth/github/webhooks/route.ts new file mode 100644 index 0000000..0d424eb --- /dev/null +++ b/src/app/(public)/auth/github/webhooks/route.ts @@ -0,0 +1,90 @@ +import { NextResponse } from 'next/server'; +import crypto from 'crypto'; +import prisma from '@/lib/db'; + +const WEBHOOK_SECRET = process.env.GITHUB_WEBHOOK_SECRET!; + +export async function POST(request: Request) { + try { + const isValid = await validateRequest(request); + if (!isValid) { + return NextResponse.json({ error: 'Invalid signature' }, { status: 401 }); + } + + const event = request.headers.get('x-github-event'); + // returns a string if valid, false if not, so we can parse it. + const payload = JSON.parse(isValid); + + // handle installation events + if (event === 'installation') { + switch (payload.action) { + case 'created': + console.log('New installation:', payload.installation.id); + await updateInstallation(payload.sender.id, payload.installation.id); + break; + case 'deleted': + console.log('Installation deleted:', payload.installation.id); + await updateInstallation(payload.sender.id, payload.installation.id, false); + break; + } + } + + return NextResponse.json({ success: true }); + } catch (error) { + console.error('Webhook error:', error); + return NextResponse.json({ error: 'Internal server error' }, { status: 500 }); + } +} + +async function updateInstallation(userId: number, installationId: number, isAdding = true) { + const checkUserExists = await prisma.user.findUnique({ + where: { + githubId: userId.toString(), + }, + }); + if (!checkUserExists) return; + + if (isAdding) { + await prisma.user.update({ + where: { + githubId: userId.toString(), + }, + data: { + installations: { + push: installationId.toString(), + }, + }, + }); + } else { + await prisma.user.update({ + where: { + githubId: userId.toString(), + }, + data: { + installations: { + // taking advantage of the prior user request to filter + set: checkUserExists.installations.filter((id) => id !== installationId.toString()), + }, + }, + }); + } +} + +async function validateRequest(request: Request) { + const headers = request.headers; + const signature = headers.get('x-hub-signature-256'); + const body = await request.text(); + + const hmac = crypto.createHmac('sha256', WEBHOOK_SECRET); + const digest = 'sha256=' + hmac.update(body).digest('hex'); + if ( + !crypto.timingSafeEqual( + new Uint8Array(Buffer.from(signature!)), + new Uint8Array(Buffer.from(digest)) + ) + ) { + return false; + } + + return body; +} diff --git a/src/components/app/GithubRepoChooser/GithubRepoChooser.tsx b/src/components/app/GithubRepoChooser/GithubRepoChooser.tsx new file mode 100644 index 0000000..8d1a43a --- /dev/null +++ b/src/components/app/GithubRepoChooser/GithubRepoChooser.tsx @@ -0,0 +1,94 @@ +'use client'; + +import * as React from 'react'; +import { Check, ChevronsUpDown } from 'lucide-react'; + +import { cn } from '@/lib/utils'; +import { Button } from '@/components/ui/button'; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from '@/components/ui/command'; +import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; +import { getRepos } from './getRepos'; +import { set } from 'zod'; + +export default function GithubRepoChooser(props: Props) { + const [open, setOpen] = React.useState(false); + const [value, setValue] = React.useState(''); + const [repos, setRepos] = React.useState([]); + const [isLoading, setIsLoading] = React.useState(true); + + React.useEffect(() => { + setIsLoading(true); + getRepos().then((response) => { + if (response.success) { + setRepos(response.repos!); + setIsLoading(false); + } + }); + console.log(props.selected); + setValue(props.selected ?? ''); + }, []); + React.useEffect(() => { + props.onSelect(value); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [value]); + + return ( + + + + + + + + + No repos found. + + {repos.map((repo) => ( + { + console.log(currentValue, value); + setValue(currentValue === value ? '' : currentValue); + setOpen(false); + }} + > + {repo} + + + ))} + + + + + + ); +} + +interface Props { + onSelect: (repo: string) => void; + selected?: string; +} diff --git a/src/components/app/GithubRepoChooser/getRepos.ts b/src/components/app/GithubRepoChooser/getRepos.ts new file mode 100644 index 0000000..9cfcb0b --- /dev/null +++ b/src/components/app/GithubRepoChooser/getRepos.ts @@ -0,0 +1,41 @@ +'use server'; + +import { validateRequest } from '@/lib/auth'; +import { octokitApp } from '@/lib/octokitApp'; + +export async function getRepos() { + const { user } = await validateRequest(); + if (!user) { + return { success: false, error: 'You must be logged in' }; + } + + const repoList: Array<{ name: string; pushed_at: string }> = []; + + for (const installation of user.installations) { + const octokit = await octokitApp.getInstallationOctokit(Number(installation)); + const { data: repos } = await octokit.request('GET /installation/repositories', { + per_page: 100, + }); + if (repos.repositories.length >= 100) { + await octokit + .request('GET /installation/repositories', { + per_page: 100, + page: 2, + }) + .then(({ data }) => repos.repositories.push(...data.repositories)); + } + + const repoData = repos.repositories.map((repo) => ({ + name: repo.full_name, + pushed_at: repo.pushed_at ?? '1970-01-01T00:00:00Z', + })); + + repoList.push(...repoData); + } + + const sortedRepos = repoList + .sort((a, b) => new Date(b.pushed_at).getTime() - new Date(a.pushed_at).getTime()) + .map((repo) => repo.name); + + return { success: true, repos: sortedRepos }; +} diff --git a/src/components/app/ProjectSettings/ProjectSettings.tsx b/src/components/app/ProjectSettings/ProjectSettings.tsx index 1a762fc..13abbee 100644 --- a/src/components/app/ProjectSettings/ProjectSettings.tsx +++ b/src/components/app/ProjectSettings/ProjectSettings.tsx @@ -4,7 +4,13 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/com import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import type { Project } from '@prisma/client'; import { UniversalForm } from '../UniversalForm/UniversalForm'; -import { customData, editProject, ratelimitChange } from '@/lib/forms/actions'; +import { + customData, + editProject, + githubSettings, + githubTestIssue, + ratelimitChange, +} from '@/lib/forms/actions'; import { bodyGen, bodyGenNoIdent } from '@/lib/bodyGen'; import { Breadcrumb, @@ -14,9 +20,16 @@ import { BreadcrumbPage, BreadcrumbSeparator, } from '@/components/ui/breadcrumb'; +import GithubRepoChooser from '../GithubRepoChooser/GithubRepoChooser'; +import React from 'react'; +import Link from 'next/link'; +import { useRouter } from 'next/navigation'; export default function ProjectSettings(project: Project) { - const url = `https://${window.location.hostname}/api/feedback/${project.id}`; + const router = useRouter(); + const [ghRepo, setGhRepo] = React.useState(''); + const apiUrl = `https://${window.location.hostname}/api/feedback/${project.id}`; + return (
@@ -109,8 +122,81 @@ export default function ProjectSettings(project: Project) {
- -

Soon™

+ + + + GitHub Integration + Connect your project to GitHub + + + { + setGhRepo(`https://github.com/${repo}`); + }} + selected={project.github!.replace('https://github.com/', '')} + /> +

+ Not the results you were expecting? You may have not allowed your user in the{' '} + + installation settings + + . +

+ router.refresh()} + /> +
+
+ + + Issue submission testing + Make sure your setup works! + + + {project.github ? ( + + ) : ( +

+ You need to connect your project to a GitHub repository before you can test issue + submission. +

+ )} +
+
@@ -123,20 +209,13 @@ export default function ProjectSettings(project: Project) {

Endpoint

- {url} + {apiUrl}

Method

POST

- {/*
-

Headers

- - {`Content-Type: application/json - Authorization: Bearer YOUR_API_KEY`} - -
*/}

Body

@@ -148,7 +227,7 @@ export default function ProjectSettings(project: Project) { {stripIndents`curl -X POST \\ -d '${bodyGenNoIdent(project.customData)}' \\ - ${url}`} + ${apiUrl}`}
@@ -156,9 +235,7 @@ export default function ProjectSettings(project: Project) { Rate limiting - - Manage your API rate limits. - + Manage your API rate limits. ({ diff --git a/src/components/app/UniversalForm/zod.ts b/src/components/app/UniversalForm/zod.ts index fd37724..d7ee634 100644 --- a/src/components/app/UniversalForm/zod.ts +++ b/src/components/app/UniversalForm/zod.ts @@ -1,18 +1,39 @@ -import { z } from "zod"; +import { z } from 'zod'; export const projectSettingsSchema = z.object({ id: z.string().nonempty(), name: z.string().nonempty(), description: z.string().nonempty(), -}) +}); export const ratelimitChangeSchema = z.object({ id: z.string().nonempty(), - requests: z.string().nonempty().transform((val) => parseInt(val, 10)), - duration: z.string().nonempty().transform((val) => parseInt(val, 10)) -}) + requests: z + .string() + .nonempty() + .transform((val) => parseInt(val, 10)), + duration: z + .string() + .nonempty() + .transform((val) => parseInt(val, 10)), +}); export const customDataSchema = z.object({ id: z.string().nonempty(), - data: z.string().nonempty() -}) \ No newline at end of file + data: z.string().nonempty(), +}); + +export const githubSettingsSchema = z.object({ + id: z.string().nonempty(), + github: z + .string() + .regex( + /^https:\/\/github\.com\/[^\s\/]+\/[^\s\/]+[/]?$/, + 'Github URL must be "https://github.com/user/repo"' + ) + .nonempty(), +}); + +export const githubTestIssueSchema = z.object({ + id: z.string().nonempty(), +}); \ No newline at end of file diff --git a/src/components/ui/command.tsx b/src/components/ui/command.tsx new file mode 100644 index 0000000..59a2645 --- /dev/null +++ b/src/components/ui/command.tsx @@ -0,0 +1,153 @@ +"use client" + +import * as React from "react" +import { type DialogProps } from "@radix-ui/react-dialog" +import { Command as CommandPrimitive } from "cmdk" +import { Search } from "lucide-react" + +import { cn } from "@/lib/utils" +import { Dialog, DialogContent } from "@/components/ui/dialog" + +const Command = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +Command.displayName = CommandPrimitive.displayName + +const CommandDialog = ({ children, ...props }: DialogProps) => { + return ( + + + + {children} + + + + ) +} + +const CommandInput = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( +
+ + +
+)) + +CommandInput.displayName = CommandPrimitive.Input.displayName + +const CommandList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) + +CommandList.displayName = CommandPrimitive.List.displayName + +const CommandEmpty = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>((props, ref) => ( + +)) + +CommandEmpty.displayName = CommandPrimitive.Empty.displayName + +const CommandGroup = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) + +CommandGroup.displayName = CommandPrimitive.Group.displayName + +const CommandSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +CommandSeparator.displayName = CommandPrimitive.Separator.displayName + +const CommandItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) + +CommandItem.displayName = CommandPrimitive.Item.displayName + +const CommandShortcut = ({ + className, + ...props +}: React.HTMLAttributes) => { + return ( + + ) +} +CommandShortcut.displayName = "CommandShortcut" + +export { + Command, + CommandDialog, + CommandInput, + CommandList, + CommandEmpty, + CommandGroup, + CommandItem, + CommandShortcut, + CommandSeparator, +} diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx new file mode 100644 index 0000000..01ff19c --- /dev/null +++ b/src/components/ui/dialog.tsx @@ -0,0 +1,122 @@ +"use client" + +import * as React from "react" +import * as DialogPrimitive from "@radix-ui/react-dialog" +import { X } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Dialog = DialogPrimitive.Root + +const DialogTrigger = DialogPrimitive.Trigger + +const DialogPortal = DialogPrimitive.Portal + +const DialogClose = DialogPrimitive.Close + +const DialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName + +const DialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + {children} + + + Close + + + +)) +DialogContent.displayName = DialogPrimitive.Content.displayName + +const DialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DialogHeader.displayName = "DialogHeader" + +const DialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DialogFooter.displayName = "DialogFooter" + +const DialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogTitle.displayName = DialogPrimitive.Title.displayName + +const DialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogDescription.displayName = DialogPrimitive.Description.displayName + +export { + Dialog, + DialogPortal, + DialogOverlay, + DialogClose, + DialogTrigger, + DialogContent, + DialogHeader, + DialogFooter, + DialogTitle, + DialogDescription, +} diff --git a/src/components/ui/popover.tsx b/src/components/ui/popover.tsx new file mode 100644 index 0000000..a0ec48b --- /dev/null +++ b/src/components/ui/popover.tsx @@ -0,0 +1,31 @@ +"use client" + +import * as React from "react" +import * as PopoverPrimitive from "@radix-ui/react-popover" + +import { cn } from "@/lib/utils" + +const Popover = PopoverPrimitive.Root + +const PopoverTrigger = PopoverPrimitive.Trigger + +const PopoverContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( + + + +)) +PopoverContent.displayName = PopoverPrimitive.Content.displayName + +export { Popover, PopoverTrigger, PopoverContent } diff --git a/src/lib/auth/index.ts b/src/lib/auth/index.ts index 3e24d6b..55f01ec 100644 --- a/src/lib/auth/index.ts +++ b/src/lib/auth/index.ts @@ -28,6 +28,7 @@ export const lucia = new Lucia(adapter, { return { githubId: attributes.githubId, username: attributes.username, + installations: attributes.installations, }; }, }); @@ -69,4 +70,5 @@ declare module 'lucia' { interface DatabaseUserAttributes { githubId: string; username: string; + installations: string[]; } diff --git a/src/lib/bodyGen.ts b/src/lib/bodyGen.ts index e5524c1..6597a22 100644 --- a/src/lib/bodyGen.ts +++ b/src/lib/bodyGen.ts @@ -6,6 +6,5 @@ export const bodyGen = (customData: string[]) => { }; export const bodyGenNoIdent = (customData: string[]) => { - console.log(bodyGen(customData)); return JSON.stringify(JSON.parse(bodyGen(customData))); } \ No newline at end of file diff --git a/src/lib/forms/actions.ts b/src/lib/forms/actions.ts index ec4ecfe..739d747 100644 --- a/src/lib/forms/actions.ts +++ b/src/lib/forms/actions.ts @@ -1,16 +1,24 @@ -'use server' +'use server'; -import { customDataSchema, projectSettingsSchema, ratelimitChangeSchema } from "@/components/app/UniversalForm/zod"; -import { validateRequest } from "../auth"; -import prisma from "../db"; -import zodVerify from "../zodVerify"; -import { createSchema } from "./zod"; +import { + customDataSchema, + githubSettingsSchema, + githubTestIssueSchema, + projectSettingsSchema, + ratelimitChangeSchema, +} from '@/components/app/UniversalForm/zod'; +import { validateRequest } from '../auth'; +import prisma from '../db'; +import zodVerify from '../zodVerify'; +import { createSchema } from './zod'; +import { Octokit } from '@octokit/core'; +import { octokitApp } from '../octokitApp'; export async function create(prev: any, formData: FormData) { const zod = await zodVerify(createSchema, formData); const { user } = await validateRequest(); if (!user) { - return { success: false, error: "You must be logged in" }; + return { success: false, error: 'You must be logged in' }; } if (!zod.success) { return zod; @@ -24,7 +32,7 @@ export async function create(prev: any, formData: FormData) { id: user.id, }, }, - } + }, }); return { success: true, id: dbCreate.id }; } @@ -33,7 +41,7 @@ export async function editProject(prev: any, formData: FormData) { const zod = await zodVerify(projectSettingsSchema, formData); const { user } = await validateRequest(); if (!user) { - return { success: false, error: "You must be logged in" }; + return { success: false, error: 'You must be logged in' }; } if (!zod.success) { return zod; @@ -45,7 +53,7 @@ export async function editProject(prev: any, formData: FormData) { }, data: { ...zod.data, - } + }, }); return { success: true, id: dbUpdate.id }; } @@ -55,7 +63,7 @@ export async function ratelimitChange(prev: any, formData: FormData) { const zod = await zodVerify(ratelimitChangeSchema, formData); const { user } = await validateRequest(); if (!user) { - return { success: false, error: "You must be logged in" }; + return { success: false, error: 'You must be logged in' }; } if (!zod.success) { return zod; @@ -68,7 +76,7 @@ export async function ratelimitChange(prev: any, formData: FormData) { data: { rateLimitReq: zod.data.requests, rateLimitTime: zod.data.duration, - } + }, }); return { success: true, id: dbUpdate.id }; } @@ -77,7 +85,7 @@ export async function customData(prev: any, formData: FormData) { const zod = await zodVerify(customDataSchema, formData); const { user } = await validateRequest(); if (!user) { - return { success: false, error: "You must be logged in" }; + return { success: false, error: 'You must be logged in' }; } if (!zod.success) { return zod; @@ -92,7 +100,86 @@ export async function customData(prev: any, formData: FormData) { }, data: { customData: splitted, - } + }, }); return { success: true, id: dbUpdate.id }; } + +export async function githubSettings(prev: any, formData: FormData) { + const zod = await zodVerify(githubSettingsSchema, formData); + const { user, session } = await validateRequest(); + if (!user) { + return { success: false, error: 'You must be logged in' }; + } + if (!zod.success) { + return zod; + } + + const dbUpdate = await prisma.project.update({ + where: { + id: zod.data.id, + }, + data: { + github: zod.data.github, + }, + }); + return { success: true, id: dbUpdate.id }; +} + +export async function githubTestIssue(prev: any, formData: FormData) { + const zod = await zodVerify(githubTestIssueSchema, formData); + const { user } = await validateRequest(); + if (!user) { + return { success: false, error: 'You must be logged in' }; + } + if (!zod.success) { + return zod; + } + + const project = await prisma.project.findUnique({ + where: { + id: zod.data.id, + }, + include: { + user: true, + }, + }); + if (!project) { + return { success: false, error: 'Project not found' }; + } + + try { + const [owner, repo] = project.github!.split('/').slice(-2); + let issueCreated = false; + + for (const installationId of project.user.installations) { + if (issueCreated) break; + + const installation = await octokitApp.getInstallationOctokit(Number(installationId)); + const getRepo = await installation + .request('GET /repos/{owner}/{repo}', { + owner, + repo, + }) + .catch(() => ({ status: 404 })); + if (getRepo.status === 200) { + const createIssue = await installation.request('POST /repos/{owner}/{repo}/issues', { + owner, + repo, + title: 'Test issue', + body: "### You are all set! 🎉\n\nIf you're reading this, the test issue has been created successfully!", + }); + + if (createIssue.status === 201) { + issueCreated = true; + break; + } + } + } + } catch (e) { + console.error(e); + return { success: false, error: e }; + } + + return { success: true }; +} diff --git a/src/lib/forms/zod.ts b/src/lib/forms/zod.ts index f59144b..010e73b 100644 --- a/src/lib/forms/zod.ts +++ b/src/lib/forms/zod.ts @@ -3,6 +3,5 @@ import { z } from "zod"; export const createSchema = z.object({ name: z.string().nonempty(), description: z.string().nonempty(), - github: z.string().url().nonempty(), }); export type createSchemaType = z.infer; \ No newline at end of file diff --git a/src/lib/octokitApp.ts b/src/lib/octokitApp.ts new file mode 100644 index 0000000..e510b88 --- /dev/null +++ b/src/lib/octokitApp.ts @@ -0,0 +1,11 @@ +import { App } from "@octokit/app"; +import { readFileSync } from "fs"; + +export const octokitApp = new App({ + appId: process.env.GITHUB_APP_ID!, + privateKey: readFileSync('./github-app.pem').toString(), + oauth: { + clientId: process.env.GITHUB_CLIENT_ID!, + clientSecret: process.env.GITHUB_CLIENT_SECRET!, + }, +}); \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 31a61aa..08d1ae7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -912,12 +912,79 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@octokit/app@^15.1.1": + version "15.1.1" + resolved "https://registry.yarnpkg.com/@octokit/app/-/app-15.1.1.tgz#bbbae2b209928c8ca038122ec85bff67045628c7" + integrity sha512-fk8xrCSPTJGpyBdBNI+DcZ224dm0aApv4vi6X7/zTmANXlegKV2Td+dJ+fd7APPaPN7R+xttUsj2Fm+AFDSfMQ== + dependencies: + "@octokit/auth-app" "^7.0.0" + "@octokit/auth-unauthenticated" "^6.0.0" + "@octokit/core" "^6.1.2" + "@octokit/oauth-app" "^7.0.0" + "@octokit/plugin-paginate-rest" "^11.0.0" + "@octokit/types" "^13.0.0" + "@octokit/webhooks" "^13.0.0" + +"@octokit/auth-app@^7.0.0": + version "7.1.3" + resolved "https://registry.yarnpkg.com/@octokit/auth-app/-/auth-app-7.1.3.tgz#b6b26e880895eaa4effc7d38a4679f05e100f593" + integrity sha512-GZdkOp2kZTIy5dG9oXqvzUAZiPvDx4C/lMlN6yQjtG9d/+hYa7W8WXTJoOrXE8UdfL9A/sZMl206dmtkl9lwVQ== + 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" + toad-cache "^3.7.0" + universal-github-app-jwt "^2.2.0" + universal-user-agent "^7.0.0" + +"@octokit/auth-oauth-app@^8.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@^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@^6.1.2": +"@octokit/auth-unauthenticated@^6.0.0", "@octokit/auth-unauthenticated@^6.0.0-beta.1": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@octokit/auth-unauthenticated/-/auth-unauthenticated-6.1.0.tgz#de0fe923bb06ed93aea526ab99972a98c546d0bf" + integrity sha512-zPSmfrUAcspZH/lOFQnVnvjQZsIvmfApQH6GzJrkIunDooU1Su2qt2FfMTSVPRp7WLTQyC20Kd55lF+mIYaohQ== + dependencies: + "@octokit/request-error" "^6.0.1" + "@octokit/types" "^13.0.0" + +"@octokit/core@^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== @@ -947,19 +1014,60 @@ "@octokit/types" "^13.0.0" universal-user-agent "^7.0.0" +"@octokit/oauth-app@^7.0.0": + version "7.1.3" + resolved "https://registry.yarnpkg.com/@octokit/oauth-app/-/oauth-app-7.1.3.tgz#a0f256dd185e7c00bfbc3e6bc3c5aad66e42c609" + integrity sha512-EHXbOpBkSGVVGF1W+NLMmsnSsJRkcrnVmDKt0TQYRBb6xWfWzoi9sBD4DIqZ8jGhOWO/V8t4fqFyJ4vDQDn9bg== + dependencies: + "@octokit/auth-oauth-app" "^8.0.0" + "@octokit/auth-oauth-user" "^5.0.1" + "@octokit/auth-unauthenticated" "^6.0.0-beta.1" + "@octokit/core" "^6.0.0" + "@octokit/oauth-authorization-url" "^7.0.0" + "@octokit/oauth-methods" "^5.0.0" + "@types/aws-lambda" "^8.10.83" + 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@^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/request-error@^6.0.1": +"@octokit/openapi-webhooks-types@8.5.1": + version "8.5.1" + resolved "https://registry.yarnpkg.com/@octokit/openapi-webhooks-types/-/openapi-webhooks-types-8.5.1.tgz#de421dbd3efb586e908a152eed3f0ae50698a2f2" + integrity sha512-i3h1b5zpGSB39ffBbYdSGuAd0NhBAwPyA3QV3LYi/lx4lsbZiu7u2UHgXVUR6EpvOI8REOuVh1DZTRfHoJDvuQ== + +"@octokit/plugin-paginate-rest@^11.0.0": + version "11.3.6" + resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.3.6.tgz#82f33c87464202423c2a89d5cc8c38761f4aa86b" + integrity sha512-zcvqqf/+TicbTCa/Z+3w4eBJcAxCFymtc0UAIsR3dEVoNilWld4oXdscQ3laXamTszUZdusw97K8+DrbFiOwjw== + dependencies: + "@octokit/types" "^13.6.2" + +"@octokit/request-error@^6.0.1", "@octokit/request-error@^6.1.0", "@octokit/request-error@^6.1.1": version "6.1.5" resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-6.1.5.tgz#907099e341c4e6179db623a0328d678024f54653" integrity sha512-IlBTfGX8Yn/oFPMwSfvugfncK2EwRLjzbrpifNaMY8o/HTEAFqCA1FZxjD9cWvSKBHgrIhc4CSBIzMxiLsbzFQ== dependencies: "@octokit/types" "^13.0.0" -"@octokit/request@^9.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== @@ -969,13 +1077,27 @@ "@octokit/types" "^13.1.0" universal-user-agent "^7.0.2" -"@octokit/types@^13.0.0", "@octokit/types@^13.1.0": +"@octokit/types@^13.0.0", "@octokit/types@^13.1.0", "@octokit/types@^13.4.1", "@octokit/types@^13.6.2": version "13.6.2" resolved "https://registry.yarnpkg.com/@octokit/types/-/types-13.6.2.tgz#e10fc4d2bdd65d836d1ced223b03ad4cfdb525bd" integrity sha512-WpbZfZUcZU77DrSW4wbsSgTPfKcp286q3ItaIgvSbBpZJlu6mnYXAkjZz6LVZPXkEvLIM8McanyZejKTYUHipA== dependencies: "@octokit/openapi-types" "^22.2.0" +"@octokit/webhooks-methods@^5.0.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@octokit/webhooks-methods/-/webhooks-methods-5.1.0.tgz#13b6c08f89902c1ab0ddf31c6eeeec9c2772cfe6" + integrity sha512-yFZa3UH11VIxYnnoOYCVoJ3q4ChuSOk2IVBBQ0O3xtKX4x9bmKb/1t+Mxixv2iUhzMdOl1qeWJqEhouXXzB3rQ== + +"@octokit/webhooks@^13.0.0": + version "13.4.1" + resolved "https://registry.yarnpkg.com/@octokit/webhooks/-/webhooks-13.4.1.tgz#608929916b0e0e5755fa5ca1de7484d2113bc6a9" + integrity sha512-I5YPUtfWidh+OzyrlDahJsUpkpGK0kCTmDRbuqGmlCUzOtxdEkX3R4d6Cd08ijQYwkVXQJanPdbKuZBeV2NMaA== + dependencies: + "@octokit/openapi-webhooks-types" "8.5.1" + "@octokit/request-error" "^6.0.1" + "@octokit/webhooks-methods" "^5.0.0" + "@oslojs/asn1@1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@oslojs/asn1/-/asn1-1.0.0.tgz#25edb31585b369efdc103e9a1eb822df9c235174" @@ -1059,6 +1181,13 @@ dependencies: "@prisma/debug" "6.0.1" +"@radix-ui/primitive@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.0.1.tgz#e46f9958b35d10e9f6dc71c497305c22e3e55dbd" + integrity sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/primitive@1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.1.0.tgz#42ef83b3b56dccad5d703ae8c42919a68798bbe2" @@ -1076,6 +1205,13 @@ dependencies: "@radix-ui/react-primitive" "2.0.0" +"@radix-ui/react-arrow@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-arrow/-/react-arrow-1.1.1.tgz#2103721933a8bfc6e53bbfbdc1aaad5fc8ba0dd7" + integrity sha512-NaVpZfmv8SKeZbn4ijN2V3jlHA9ngBG16VnIIm22nUR0Yk8KUALyBxT3KYEUnNuch9sTE8UTsS3whzBgKOL30w== + dependencies: + "@radix-ui/react-primitive" "2.0.1" + "@radix-ui/react-avatar@^1.0.4": version "1.0.4" resolved "https://registry.yarnpkg.com/@radix-ui/react-avatar/-/react-avatar-1.0.4.tgz#de9a5349d9e3de7bbe990334c4d2011acbbb9623" @@ -1141,11 +1277,64 @@ resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.1.1.tgz#82074aa83a472353bb22e86f11bcbd1c61c4c71a" integrity sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q== +"@radix-ui/react-dialog@1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-1.0.5.tgz#71657b1b116de6c7a0b03242d7d43e01062c7300" + integrity sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/primitive" "1.0.1" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-context" "1.0.1" + "@radix-ui/react-dismissable-layer" "1.0.5" + "@radix-ui/react-focus-guards" "1.0.1" + "@radix-ui/react-focus-scope" "1.0.4" + "@radix-ui/react-id" "1.0.1" + "@radix-ui/react-portal" "1.0.4" + "@radix-ui/react-presence" "1.0.1" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-slot" "1.0.2" + "@radix-ui/react-use-controllable-state" "1.0.1" + aria-hidden "^1.1.1" + react-remove-scroll "2.5.5" + +"@radix-ui/react-dialog@^1.1.4": + version "1.1.4" + resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-1.1.4.tgz#d68e977acfcc0d044b9dab47b6dd2c179d2b3191" + integrity sha512-Ur7EV1IwQGCyaAuyDRiOLA5JIUZxELJljF+MbM/2NC0BYwfuRrbpS30BiQBJrVruscgUkieKkqXYDOoByaxIoA== + dependencies: + "@radix-ui/primitive" "1.1.1" + "@radix-ui/react-compose-refs" "1.1.1" + "@radix-ui/react-context" "1.1.1" + "@radix-ui/react-dismissable-layer" "1.1.3" + "@radix-ui/react-focus-guards" "1.1.1" + "@radix-ui/react-focus-scope" "1.1.1" + "@radix-ui/react-id" "1.1.0" + "@radix-ui/react-portal" "1.1.3" + "@radix-ui/react-presence" "1.1.2" + "@radix-ui/react-primitive" "2.0.1" + "@radix-ui/react-slot" "1.1.1" + "@radix-ui/react-use-controllable-state" "1.1.0" + aria-hidden "^1.1.1" + react-remove-scroll "^2.6.1" + "@radix-ui/react-direction@1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@radix-ui/react-direction/-/react-direction-1.1.0.tgz#a7d39855f4d077adc2a1922f9c353c5977a09cdc" integrity sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg== +"@radix-ui/react-dismissable-layer@1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.5.tgz#3f98425b82b9068dfbab5db5fff3df6ebf48b9d4" + integrity sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/primitive" "1.0.1" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-use-callback-ref" "1.0.1" + "@radix-ui/react-use-escape-keydown" "1.0.3" + "@radix-ui/react-dismissable-layer@1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.1.tgz#cbdcb739c5403382bdde5f9243042ba643883396" @@ -1157,6 +1346,17 @@ "@radix-ui/react-use-callback-ref" "1.1.0" "@radix-ui/react-use-escape-keydown" "1.1.0" +"@radix-ui/react-dismissable-layer@1.1.3": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.3.tgz#4ee0f0f82d53bf5bd9db21665799bb0d1bad5ed8" + integrity sha512-onrWn/72lQoEucDmJnr8uczSNTujT0vJnA/X5+3AkChVPowr8n1yvIKIabhWyMQeMvvmdpsvcyDqx3X1LEXCPg== + dependencies: + "@radix-ui/primitive" "1.1.1" + "@radix-ui/react-compose-refs" "1.1.1" + "@radix-ui/react-primitive" "2.0.1" + "@radix-ui/react-use-callback-ref" "1.1.0" + "@radix-ui/react-use-escape-keydown" "1.1.0" + "@radix-ui/react-dropdown-menu@^2.1.2": version "2.1.2" resolved "https://registry.yarnpkg.com/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.2.tgz#acc49577130e3c875ef0133bd1e271ea3392d924" @@ -1170,11 +1370,28 @@ "@radix-ui/react-primitive" "2.0.0" "@radix-ui/react-use-controllable-state" "1.1.0" +"@radix-ui/react-focus-guards@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.1.tgz#1ea7e32092216b946397866199d892f71f7f98ad" + integrity sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-focus-guards@1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.1.tgz#8635edd346304f8b42cae86b05912b61aef27afe" integrity sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg== +"@radix-ui/react-focus-scope@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.4.tgz#2ac45fce8c5bb33eb18419cdc1905ef4f1906525" + integrity sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-use-callback-ref" "1.0.1" + "@radix-ui/react-focus-scope@1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.0.tgz#ebe2891a298e0a33ad34daab2aad8dea31caf0b2" @@ -1184,6 +1401,23 @@ "@radix-ui/react-primitive" "2.0.0" "@radix-ui/react-use-callback-ref" "1.1.0" +"@radix-ui/react-focus-scope@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.1.tgz#5c602115d1db1c4fcfa0fae4c3b09bb8919853cb" + integrity sha512-01omzJAYRxXdG2/he/+xy+c8a8gCydoQ1yOxnWNcRhrrBW5W+RQJ22EK1SaO8tb3WoUsuEw7mJjBozPzihDFjA== + dependencies: + "@radix-ui/react-compose-refs" "1.1.1" + "@radix-ui/react-primitive" "2.0.1" + "@radix-ui/react-use-callback-ref" "1.1.0" + +"@radix-ui/react-id@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-1.0.1.tgz#73cdc181f650e4df24f0b6a5b7aa426b912c88c0" + integrity sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-use-layout-effect" "1.0.1" + "@radix-ui/react-id@1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-1.1.0.tgz#de47339656594ad722eb87f94a6b25f9cffae0ed" @@ -1222,6 +1456,27 @@ aria-hidden "^1.1.1" react-remove-scroll "2.6.0" +"@radix-ui/react-popover@^1.1.4": + version "1.1.4" + resolved "https://registry.yarnpkg.com/@radix-ui/react-popover/-/react-popover-1.1.4.tgz#d83104e5fb588870a673b55f3387da4844e5836e" + integrity sha512-aUACAkXx8LaFymDma+HQVji7WhvEhpFJ7+qPz17Nf4lLZqtreGOFRiNQWQmhzp7kEWg9cOyyQJpdIMUMPc/CPw== + dependencies: + "@radix-ui/primitive" "1.1.1" + "@radix-ui/react-compose-refs" "1.1.1" + "@radix-ui/react-context" "1.1.1" + "@radix-ui/react-dismissable-layer" "1.1.3" + "@radix-ui/react-focus-guards" "1.1.1" + "@radix-ui/react-focus-scope" "1.1.1" + "@radix-ui/react-id" "1.1.0" + "@radix-ui/react-popper" "1.2.1" + "@radix-ui/react-portal" "1.1.3" + "@radix-ui/react-presence" "1.1.2" + "@radix-ui/react-primitive" "2.0.1" + "@radix-ui/react-slot" "1.1.1" + "@radix-ui/react-use-controllable-state" "1.1.0" + aria-hidden "^1.1.1" + react-remove-scroll "^2.6.1" + "@radix-ui/react-popper@1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-1.2.0.tgz#a3e500193d144fe2d8f5d5e60e393d64111f2a7a" @@ -1238,6 +1493,30 @@ "@radix-ui/react-use-size" "1.1.0" "@radix-ui/rect" "1.1.0" +"@radix-ui/react-popper@1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-1.2.1.tgz#2fc66cfc34f95f00d858924e3bee54beae2dff0a" + integrity sha512-3kn5Me69L+jv82EKRuQCXdYyf1DqHwD2U/sxoNgBGCB7K9TRc3bQamQ+5EPM9EvyPdli0W41sROd+ZU1dTCztw== + dependencies: + "@floating-ui/react-dom" "^2.0.0" + "@radix-ui/react-arrow" "1.1.1" + "@radix-ui/react-compose-refs" "1.1.1" + "@radix-ui/react-context" "1.1.1" + "@radix-ui/react-primitive" "2.0.1" + "@radix-ui/react-use-callback-ref" "1.1.0" + "@radix-ui/react-use-layout-effect" "1.1.0" + "@radix-ui/react-use-rect" "1.1.0" + "@radix-ui/react-use-size" "1.1.0" + "@radix-ui/rect" "1.1.0" + +"@radix-ui/react-portal@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-1.0.4.tgz#df4bfd353db3b1e84e639e9c63a5f2565fb00e15" + integrity sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-portal@1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-1.1.2.tgz#51eb46dae7505074b306ebcb985bf65cc547d74e" @@ -1246,6 +1525,23 @@ "@radix-ui/react-primitive" "2.0.0" "@radix-ui/react-use-layout-effect" "1.1.0" +"@radix-ui/react-portal@1.1.3": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-1.1.3.tgz#b0ea5141103a1671b715481b13440763d2ac4440" + integrity sha512-NciRqhXnGojhT93RPyDaMPfLH3ZSl4jjIFbZQ1b/vxvZEdHsBZ49wP9w8L3HzUQwep01LcWtkUvm0OVB5JAHTw== + dependencies: + "@radix-ui/react-primitive" "2.0.1" + "@radix-ui/react-use-layout-effect" "1.1.0" + +"@radix-ui/react-presence@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-1.0.1.tgz#491990ba913b8e2a5db1b06b203cb24b5cdef9ba" + integrity sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-use-layout-effect" "1.0.1" + "@radix-ui/react-presence@1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-1.1.1.tgz#98aba423dba5e0c687a782c0669dcd99de17f9b1" @@ -1362,6 +1658,14 @@ resolved "https://registry.yarnpkg.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz#bce938ca413675bc937944b0d01ef6f4a6dc5bf1" integrity sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw== +"@radix-ui/react-use-controllable-state@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.1.tgz#ecd2ced34e6330caf89a82854aa2f77e07440286" + integrity sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-use-callback-ref" "1.0.1" + "@radix-ui/react-use-controllable-state@1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz#1321446857bb786917df54c0d4d084877aab04b0" @@ -1369,6 +1673,14 @@ dependencies: "@radix-ui/react-use-callback-ref" "1.1.0" +"@radix-ui/react-use-escape-keydown@1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.3.tgz#217b840c250541609c66f67ed7bab2b733620755" + integrity sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-use-callback-ref" "1.0.1" + "@radix-ui/react-use-escape-keydown@1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz#31a5b87c3b726504b74e05dac1edce7437b98754" @@ -1453,6 +1765,11 @@ dependencies: tslib "^2.4.0" +"@types/aws-lambda@^8.10.83": + version "8.10.146" + resolved "https://registry.yarnpkg.com/@types/aws-lambda/-/aws-lambda-8.10.146.tgz#05f9968d8cd9719a0a86526baf889c25761f60b8" + integrity sha512-3BaDXYTh0e6UCJYL/jwV/3+GRslSc08toAiZSmleYtkAUyV5rtvdPYxrG/88uqvTuT6sb27WE9OS90ZNTIuQ0g== + "@types/json5@^0.0.29": version "0.0.29" resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" @@ -1993,6 +2310,14 @@ cluster-key-slot@^1.1.0: resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz#88ddaa46906e303b5de30d3153b7d9fe0a0c19ac" integrity sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA== +cmdk@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/cmdk/-/cmdk-1.0.0.tgz#0a095fdafca3dfabed82d1db78a6262fb163ded9" + integrity sha512-gDzVf0a09TvoJ5jnuPvygTB77+XdOSwEmJ88L6XPFPlv7T3RxbP9jgenfylrAMD0+Le1aO0nVjQUzl2g+vjz5Q== + dependencies: + "@radix-ui/react-dialog" "1.0.5" + "@radix-ui/react-primitive" "1.0.3" + code-block-writer@^12.0.0: version "12.0.0" resolved "https://registry.yarnpkg.com/code-block-writer/-/code-block-writer-12.0.0.tgz#4dd58946eb4234105aff7f0035977b2afdc2a770" @@ -4240,6 +4565,14 @@ react-is@^16.13.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== +react-remove-scroll-bar@^2.3.3, react-remove-scroll-bar@^2.3.7: + version "2.3.8" + resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz#99c20f908ee467b385b68a3469b4a3e750012223" + integrity sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q== + dependencies: + react-style-singleton "^2.2.2" + tslib "^2.0.0" + react-remove-scroll-bar@^2.3.6: version "2.3.6" resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz#3e585e9d163be84a010180b18721e851ac81a29c" @@ -4248,6 +4581,17 @@ react-remove-scroll-bar@^2.3.6: react-style-singleton "^2.2.1" tslib "^2.0.0" +react-remove-scroll@2.5.5: + version "2.5.5" + resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz#1e31a1260df08887a8a0e46d09271b52b3a37e77" + integrity sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw== + dependencies: + react-remove-scroll-bar "^2.3.3" + react-style-singleton "^2.2.1" + tslib "^2.1.0" + use-callback-ref "^1.3.0" + use-sidecar "^1.1.2" + react-remove-scroll@2.6.0: version "2.6.0" resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.6.0.tgz#fb03a0845d7768a4f1519a99fdb84983b793dc07" @@ -4259,6 +4603,17 @@ react-remove-scroll@2.6.0: use-callback-ref "^1.3.0" use-sidecar "^1.1.2" +react-remove-scroll@^2.6.1: + version "2.6.2" + resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.6.2.tgz#2518d2c5112e71ea8928f1082a58459b5c7a2a97" + integrity sha512-KmONPx5fnlXYJQqC62Q+lwIeAk64ws/cUw6omIumRzMRPqgnYqhSSti99nbj0Ry13bv7dF+BKn7NB+OqkdZGTw== + dependencies: + react-remove-scroll-bar "^2.3.7" + react-style-singleton "^2.2.1" + tslib "^2.1.0" + use-callback-ref "^1.3.3" + use-sidecar "^1.1.2" + react-style-singleton@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/react-style-singleton/-/react-style-singleton-2.2.1.tgz#f99e420492b2d8f34d38308ff660b60d0b1205b4" @@ -4268,6 +4623,14 @@ react-style-singleton@^2.2.1: invariant "^2.2.4" tslib "^2.0.0" +react-style-singleton@^2.2.2: + version "2.2.3" + resolved "https://registry.yarnpkg.com/react-style-singleton/-/react-style-singleton-2.2.3.tgz#4265608be69a4d70cfe3047f2c6c88b2c3ace388" + integrity sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ== + dependencies: + get-nonce "^1.0.0" + tslib "^2.0.0" + react@^19.0.0-rc.1: version "19.0.0-rc.1" resolved "https://registry.yarnpkg.com/react/-/react-19.0.0-rc.1.tgz#469cc8ae6bdba224dcaab10ad6c4d90843f21352" @@ -4941,6 +5304,11 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" +toad-cache@^3.7.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/toad-cache/-/toad-cache-3.7.0.tgz#b9b63304ea7c45ec34d91f1d2fa513517025c441" + integrity sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw== + ts-api-utils@^1.3.0: version "1.4.3" resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.4.3.tgz#bfc2215fe6528fecab2b0fba570a2e8a4263b064" @@ -5089,6 +5457,11 @@ undici-types@~5.26.4: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== +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@^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" @@ -5121,6 +5494,13 @@ use-callback-ref@^1.3.0: dependencies: tslib "^2.0.0" +use-callback-ref@^1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.3.3.tgz#98d9fab067075841c5b2c6852090d5d0feabe2bf" + integrity sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg== + dependencies: + tslib "^2.0.0" + use-sidecar@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.1.2.tgz#2f43126ba2d7d7e117aa5855e5d8f0276dfe73c2"