From 3d56ba51464a8516d9211492365240a36cec5f88 Mon Sep 17 00:00:00 2001 From: Tim Raderschad <tim.raderschad@gmail.com> Date: Sun, 25 Aug 2024 21:13:52 +0200 Subject: [PATCH] feat(web): add auto flag removal (#161) --- apps/web/package.json | 10 +- apps/web/prisma/schema.prisma | 21 +- apps/web/src/api/helpers.ts | 33 + apps/web/src/api/index.ts | 4 +- apps/web/src/api/routes/integrations.ts | 94 +++ .../src/components/AddFeatureFlagModal.tsx | 4 +- apps/web/src/components/FlagPage.tsx | 42 +- .../src/components/settings/Integrations.tsx | 174 +++++ apps/web/src/components/ui/select.tsx | 49 +- apps/web/src/env/schema.mjs | 4 + .../pages/projects/[projectId]/settings.tsx | 42 +- apps/web/src/server/common/auth.ts | 22 + apps/web/src/server/common/github-app.ts | 50 ++ apps/web/src/server/common/integrations.ts | 12 + .../server/services/AiFlagRemovalService.ts | 32 + .../src/server/services/IntegrationService.ts | 3 + apps/web/src/server/trpc/router/flags.ts | 235 ++++++- apps/web/src/server/trpc/router/project.ts | 101 +++ apps/web/tsconfig.json | 2 +- pnpm-lock.yaml | 593 +++++++++--------- 20 files changed, 1173 insertions(+), 354 deletions(-) create mode 100644 apps/web/src/api/helpers.ts create mode 100644 apps/web/src/api/routes/integrations.ts create mode 100644 apps/web/src/components/settings/Integrations.tsx create mode 100644 apps/web/src/server/common/auth.ts create mode 100644 apps/web/src/server/common/github-app.ts create mode 100644 apps/web/src/server/common/integrations.ts create mode 100644 apps/web/src/server/services/AiFlagRemovalService.ts create mode 100644 apps/web/src/server/services/IntegrationService.ts diff --git a/apps/web/package.json b/apps/web/package.json index 803331c2..9c962484 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -34,7 +34,7 @@ "@monaco-editor/react": "^4.5.1", "@next-auth/prisma-adapter": "1.0.5", "@next/mdx": "14.0.4", - "@prisma/client": "5.6.0", + "@prisma/client": "5.18.0", "@radix-ui/react-avatar": "^1.0.3", "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dropdown-menu": "^2.0.6", @@ -91,6 +91,7 @@ "lodash-es": "^4.17.21", "logsnag": "^0.1.6", "lucide-react": "0.320.0", + "memoize": "^10.0.0", "micro": "^10.0.1", "ms": "^2.1.3", "next": "14.1.1", @@ -102,7 +103,9 @@ "nextjs-cors": "^2.1.2", "nodemailer": "^6.9.1", "nuqs": "^1.17.8", - "octokit": "^2.0.18", + "octokit": "^4.0.2", + "openai": "^4.56.0", + "prettier": "^2.8.7", "rate-limiter-flexible": "^5.0.3", "react": "18.2.0", "react-dom": "18.2.0", @@ -138,9 +141,8 @@ "autoprefixer": "^10.4.14", "jsdom": "^20.0.3", "postcss": "^8.4.21", - "prettier": "^2.8.7", "prettier-plugin-tailwindcss": "^0.1.13", - "prisma": "5.6.0", + "prisma": "5.18.0", "tailwindcss": "^3.3.1", "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", diff --git a/apps/web/prisma/schema.prisma b/apps/web/prisma/schema.prisma index ab3bb4d0..95b4c2a6 100644 --- a/apps/web/prisma/schema.prisma +++ b/apps/web/prisma/schema.prisma @@ -107,7 +107,8 @@ model Project { stripePriceId String? currentPeriodEnd DateTime @default(dbgenerated("(CURRENT_TIMESTAMP(3) + INTERVAL 30 DAY)")) - apiRequests ApiRequest[] + apiRequests ApiRequest[] + integrations Integration[] } model ProjectUser { @@ -310,3 +311,21 @@ model ApiRequest { @@index([createdAt]) @@index([type]) } + +enum IntegrationType { + GITHUB +} + +model Integration { + id String @id @default(cuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + type IntegrationType + settings Json + project Project @relation(fields: [projectId], references: [id], onDelete: Cascade) + projectId String + + @@unique([projectId, type]) + @@index([projectId]) +} diff --git a/apps/web/src/api/helpers.ts b/apps/web/src/api/helpers.ts new file mode 100644 index 00000000..9437a4e0 --- /dev/null +++ b/apps/web/src/api/helpers.ts @@ -0,0 +1,33 @@ +import type { Context, MiddlewareHandler } from "hono"; +import { getCookie } from "hono/cookie"; +import type { GetServerSidePropsContext } from "next"; +import type { DefaultSession } from "next-auth"; +import { getServerAuthSession } from "server/common/get-server-auth-session"; +import type { UserSession } from "types/next-auth"; + +export async function getHonoSession(c: Context) { + return await getServerAuthSession({ + req: { + ...c.req.raw.clone(), + cookies: getCookie(c), + } as unknown as GetServerSidePropsContext["req"], + res: { + ...c.res, + getHeader: (h: string) => c.req.header(h), + setHeader: (h: string, v: string) => c.header(h, v), + } as unknown as GetServerSidePropsContext["res"], + }); +} + +export const authMiddleware: MiddlewareHandler<{ + Variables: { + user: UserSession & DefaultSession["user"]; + }; +}> = async (c, next) => { + const session = await getHonoSession(c); + if (!session || !session.user) { + return c.json({ error: "Unauthorized" }, { status: 401 }); + } + c.set("user", session.user); + return next(); +}; diff --git a/apps/web/src/api/index.ts b/apps/web/src/api/index.ts index ac36ac09..5dec4ab5 100644 --- a/apps/web/src/api/index.ts +++ b/apps/web/src/api/index.ts @@ -4,6 +4,7 @@ import { Hono } from "hono"; import { cors } from "hono/cors"; import { logger } from "hono/logger"; import { makeHealthRoute } from "./routes/health"; +import { makeIntegrationsRoute } from "./routes/integrations"; import { makeLegacyProjectDataRoute } from "./routes/legacy_project_data"; import { makeEventRoute } from "./routes/v1_event"; @@ -19,4 +20,5 @@ export const app = new Hono() // v1 routes .route("/v1/config", makeConfigRoute()) .route("/v1/data", makeProjectDataRoute()) - .route("/v1/track", makeEventRoute()); + .route("/v1/track", makeEventRoute()) + .route("/integrations", makeIntegrationsRoute()); diff --git a/apps/web/src/api/routes/integrations.ts b/apps/web/src/api/routes/integrations.ts new file mode 100644 index 00000000..1c402bb6 --- /dev/null +++ b/apps/web/src/api/routes/integrations.ts @@ -0,0 +1,94 @@ +import { zValidator } from "@hono/zod-validator"; +import { authMiddleware } from "api/helpers"; +import { Hono } from "hono"; +import { githubApp } from "server/common/github-app"; +import type { GithubIntegrationSettings } from "server/common/integrations"; +import { prisma } from "server/db/client"; +import { z } from "zod"; + +export function makeIntegrationsRoute() { + return new Hono() + .get( + "/github", + zValidator( + "query", + z.object({ + projectId: z.string(), + }) + ), + authMiddleware, + async (c) => { + const user = c.get("user"); + const { projectId } = c.req.valid("query"); + const project = await prisma.project.findFirst({ + where: { id: projectId, users: { some: { userId: user.id } } }, + include: { integrations: true }, + }); + const referrer = new URL(c.req.header("Referer") ?? "/"); + + if (!project) { + referrer.searchParams.set("error", "Unauthorized"); + return c.redirect(referrer.toString()); + } + if (project.integrations.some((i) => i.type === "GITHUB")) { + referrer.searchParams.set("error", "Integration already exists"); + return c.redirect(referrer.toString()); + } + + const searchParams = new URLSearchParams(); + searchParams.set("projectId", projectId); + + return c.redirect( + await githubApp.getInstallationUrl({ state: searchParams.toString() }) + ); + } + ) + .get( + "/github/setup", + zValidator( + "query", + z.object({ + installation_id: z.string().transform(Number), + setup_action: z.enum(["install", "update"]), + state: z.string().transform((s) => { + const url = new URLSearchParams(s); + const projectId = url.get("projectId"); + if (!projectId) { + throw new Error("projectId not found in state"); + } + return { projectId }; + }), + }) + ), + async (c) => { + const { installation_id, setup_action, state } = c.req.valid("query"); + if (setup_action === "update") { + return c.json({ message: "Update not implemented" }, { status: 501 }); + } + + const project = await prisma.project.findFirst({ + where: { id: state.projectId }, + include: { integrations: true }, + }); + if (!project) { + return c.json( + { message: "Project not found" }, + { + status: 404, + } + ); + } + await prisma.integration.create({ + data: { + type: "GITHUB", + projectId: project.id, + settings: { + installationId: installation_id, + repositoryIds: [], + } satisfies GithubIntegrationSettings, + }, + }); + return c.redirect(`/projects/${project.id}/settings`); + } + ); +} diff --git a/apps/web/src/components/AddFeatureFlagModal.tsx b/apps/web/src/components/AddFeatureFlagModal.tsx index 8187e97a..8a6f964e 100644 --- a/apps/web/src/components/AddFeatureFlagModal.tsx +++ b/apps/web/src/components/AddFeatureFlagModal.tsx @@ -177,7 +177,9 @@ export const AddFeatureFlagModal = ({ const { mutateAsync } = trpc.flags.addFlag.useMutation({ onSuccess() { - ctx.flags.getFlags.invalidate({ projectId }); + ctx.flags.getFlags.invalidate({ + projectId, + }); }, }); diff --git a/apps/web/src/components/FlagPage.tsx b/apps/web/src/components/FlagPage.tsx index 22291fe5..0d190af4 100644 --- a/apps/web/src/components/FlagPage.tsx +++ b/apps/web/src/components/FlagPage.tsx @@ -1,5 +1,5 @@ import type { FeatureFlagType } from "@prisma/client"; -import type { InferQueryResult } from "@trpc/react-query/dist/utils/inferReactQueryProcedure"; +import type { inferRouterOutputs } from "@trpc/server"; import { AddFeatureFlagModal } from "components/AddFeatureFlagModal"; import { CreateEnvironmentModal } from "components/CreateEnvironmentModal"; import { @@ -15,8 +15,14 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "components/Tooltip"; import { Input } from "components/ui/input"; import Fuse from "fuse.js"; import { useProjectId } from "lib/hooks/useProjectId"; -import { EditIcon, FileEditIcon, Search, TrashIcon } from "lucide-react"; -import { useMemo, useState } from "react"; +import { + EditIcon, + FileEditIcon, + Search, + Sparkle, + TrashIcon, +} from "lucide-react"; +import { useEffect, useMemo, useState } from "react"; import { toast } from "react-hot-toast"; import { AiOutlinePlus } from "react-icons/ai"; import { BiInfoCircle } from "react-icons/bi"; @@ -155,9 +161,7 @@ export const FeatureFlagPageContent = ({ data, type, }: { - data: NonNullable< - InferQueryResult<(typeof appRouter)["flags"]["getFlags"]>["data"] - >; + data: NonNullable<inferRouterOutputs<typeof appRouter>["flags"]["getFlags"]>; type: "Flags" | "Remote Config"; }) => { const [isCreateFlagModalOpen, setIsCreateFlagModalOpen] = useState(false); @@ -170,6 +174,8 @@ export const FeatureFlagPageContent = ({ const [isCreateEnvironmentModalOpen, setIsCreateEnvironmentModalOpen] = useState(false); + const createFlagRemovalPRMutation = + trpc.flags.createFlagRemovalPR.useMutation(); const projectId = useProjectId(); @@ -178,6 +184,10 @@ export const FeatureFlagPageContent = ({ [data.flags] ); + useEffect(() => { + setFlags(data.flags); + }, [data.flags]); + const onSearch = (query: string) => { if (!query) { setFlags(data.flags); @@ -326,6 +336,26 @@ export const FeatureFlagPageContent = ({ <FileEditIcon className="mr-4 h-4 w-4" /> Edit Description </DropdownMenuItem> + <DropdownMenuItem + disabled={!data.hasGithubIntegration} + className="cursor-pointer bg-gradient-to-r from-blue-800 via-purple-600 to-pink-500 hover:from-purple-700 hover:via-pink-500 hover:to-red-400" + onClick={async () => { + const url = await toast.promise( + createFlagRemovalPRMutation.mutateAsync({ + flagId: currentFlag.id, + }), + { + loading: "Creating removal PR...", + success: "Successfully created removal PR", + error: "Failed to create removal PR", + } + ); + window.open(url, "_blank"); + }} + > + <Sparkle className="mr-4 h-4 w-4" /> + Create Removal PR + </DropdownMenuItem> <DropdownMenuItem className="cursor-pointer focus:!bg-red-700 focus:!text-white" onClick={() => { diff --git a/apps/web/src/components/settings/Integrations.tsx b/apps/web/src/components/settings/Integrations.tsx new file mode 100644 index 00000000..c0b50688 --- /dev/null +++ b/apps/web/src/components/settings/Integrations.tsx @@ -0,0 +1,174 @@ +import { Label } from "@radix-ui/react-label"; +import { LoadingSpinner } from "components/LoadingSpinner"; +import { Button } from "components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "components/ui/card"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "components/ui/command"; +import { Popover, PopoverContent, PopoverTrigger } from "components/ui/popover"; +import { cn } from "lib/utils"; +import { Check, ChevronsUpDown } from "lucide-react"; +import Link from "next/link"; +import { useState } from "react"; +import { match } from "ts-pattern"; +import { trpc } from "utils/trpc"; + +export function Integrations({ projectId }: { projectId: string | undefined }) { + const [open, setOpen] = useState(false); + const [selectedRepositoryId, setSelectedRepositoryId] = useState<string>(); + const integrationsQuery = trpc.project.getIntegrations.useQuery( + // biome-ignore lint/style/noNonNullAssertion: we check for enabled + { projectId: projectId! }, + { enabled: !!projectId } + ); + + const updateGithubIntegration = + trpc.project.updateGithubIntegration.useMutation({ + onSuccess: () => { + integrationsQuery.refetch(); + }, + }); + + if (integrationsQuery.isLoading) return <LoadingSpinner />; + if (integrationsQuery.error) return <div>Error</div>; + if (integrationsQuery.data.length === 0) { + return ( + <div> + <h1>No integrations</h1> + </div> + ); + } + + return integrationsQuery.data.map((i) => + match(i.type) + .with("GITHUB", () => { + const selectedRepository = i.potentialRepositories.find( + (r) => r.id.toString() === selectedRepositoryId + ); + + return ( + <Card key={i.id} className="max-w-lg"> + <CardHeader> + <CardTitle>Github</CardTitle> + <CardDescription> + The selected repository will be used to automagically create and + update pull requests for your feature flags. + </CardDescription> + </CardHeader> + <CardContent> + {i.installedRepos.length === 0 ? ( + <div className="flex flex-col space-y-3"> + <div className="grid gap-x-1.5"> + <Label>Selected Repository</Label> + <Popover open={open} onOpenChange={setOpen}> + <PopoverTrigger asChild> + <Button + variant="outline" + role="combobox" + aria-expanded={open} + className="w-[350px] justify-between" + > + {selectedRepository ? ( + <span> + <span className="text-muted-foreground"> + {selectedRepository.owner}/ + </span> + {selectedRepository.name} + </span> + ) : ( + "Select framework..." + )} + <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" /> + </Button> + </PopoverTrigger> + <PopoverContent className="w-[350px] p-0"> + <Command> + <CommandInput placeholder="Search repository..." /> + <CommandList> + <CommandEmpty>No framework found.</CommandEmpty> + <CommandGroup> + {i.potentialRepositories.map((repo) => ( + <CommandItem + key={repo.id} + value={`${repo.owner}/${repo.name}`} + onSelect={() => { + setSelectedRepositoryId( + repo.id.toString() === + selectedRepositoryId + ? undefined + : repo.id.toString() + ); + setOpen(false); + }} + > + <Check + className={cn( + "mr-2 h-4 w-4", + selectedRepositoryId === + repo.id.toString() + ? "opacity-100" + : "opacity-0" + )} + /> + <span> + <span className="text-muted-foreground"> + {repo.owner}/ + </span> + {repo.name} + </span> + </CommandItem> + ))} + </CommandGroup> + </CommandList> + </Command> + </PopoverContent> + </Popover> + </div> + <Button + className="ml-auto" + onClick={async () => { + if (!selectedRepositoryId) return; + await updateGithubIntegration.mutateAsync({ + integrationId: i.id, + repositoryId: Number(selectedRepositoryId), + }); + }} + > + Save + </Button> + </div> + ) : ( + <div className="flex flex-col space-y-3"> + <span>Your selected repository is</span> + + <Link + href={`https://github.com/${i.installedRepos[0]?.owner}/${i.installedRepos[0]?.name}`} + className="bg-primary-foreground mr-auto p-2 rounded-md font-mono" + > + <span className="text-muted-foreground"> + {i.installedRepos[0]?.owner}/ + </span> + {i.installedRepos[0]?.name} + </Link> + + <small>Need to change this? Contact us</small> + </div> + )} + </CardContent> + </Card> + ); + }) + .exhaustive() + ); +} diff --git a/apps/web/src/components/ui/select.tsx b/apps/web/src/components/ui/select.tsx index 7d43cb6a..342b8b6c 100644 --- a/apps/web/src/components/ui/select.tsx +++ b/apps/web/src/components/ui/select.tsx @@ -1,5 +1,5 @@ import * as SelectPrimitive from "@radix-ui/react-select"; -import { Check, ChevronDown } from "lucide-react"; +import { Check, ChevronDown, ChevronUp } from "lucide-react"; import * as React from "react"; import { cn } from "lib/utils"; @@ -17,7 +17,7 @@ const SelectTrigger = React.forwardRef< <SelectPrimitive.Trigger ref={ref} className={cn( - "flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", + "flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1", className )} {...props} @@ -30,6 +30,41 @@ const SelectTrigger = React.forwardRef< )); SelectTrigger.displayName = SelectPrimitive.Trigger.displayName; +const SelectScrollUpButton = React.forwardRef< + React.ElementRef<typeof SelectPrimitive.ScrollUpButton>, + React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton> +>(({ className, ...props }, ref) => ( + <SelectPrimitive.ScrollUpButton + ref={ref} + className={cn( + "flex cursor-default items-center justify-center py-1", + className + )} + {...props} + > + <ChevronUp className="h-4 w-4" /> + </SelectPrimitive.ScrollUpButton> +)); +SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName; + +const SelectScrollDownButton = React.forwardRef< + React.ElementRef<typeof SelectPrimitive.ScrollDownButton>, + React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton> +>(({ className, ...props }, ref) => ( + <SelectPrimitive.ScrollDownButton + ref={ref} + className={cn( + "flex cursor-default items-center justify-center py-1", + className + )} + {...props} + > + <ChevronDown className="h-4 w-4" /> + </SelectPrimitive.ScrollDownButton> +)); +SelectScrollDownButton.displayName = + SelectPrimitive.ScrollDownButton.displayName; + const SelectContent = React.forwardRef< React.ElementRef<typeof SelectPrimitive.Content>, React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content> @@ -38,7 +73,7 @@ const SelectContent = React.forwardRef< <SelectPrimitive.Content ref={ref} className={cn( - "relative z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", + "relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", position === "popper" && "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", className @@ -46,6 +81,7 @@ const SelectContent = React.forwardRef< position={position} {...props} > + <SelectScrollUpButton /> <SelectPrimitive.Viewport className={cn( "p-1", @@ -55,6 +91,7 @@ const SelectContent = React.forwardRef< > {children} </SelectPrimitive.Viewport> + <SelectScrollDownButton /> </SelectPrimitive.Content> </SelectPrimitive.Portal> )); @@ -79,12 +116,12 @@ const SelectItem = React.forwardRef< <SelectPrimitive.Item ref={ref} className={cn( - "relative flex w-full cursor-default select-none items-center rounded-sm px-4 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", + "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", className )} {...props} > - <span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center"> + <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center"> <SelectPrimitive.ItemIndicator> <Check className="h-4 w-4" /> </SelectPrimitive.ItemIndicator> @@ -116,4 +153,6 @@ export { SelectLabel, SelectItem, SelectSeparator, + SelectScrollUpButton, + SelectScrollDownButton, }; diff --git a/apps/web/src/env/schema.mjs b/apps/web/src/env/schema.mjs index 0f0c71bc..fb7ff6f3 100644 --- a/apps/web/src/env/schema.mjs +++ b/apps/web/src/env/schema.mjs @@ -31,6 +31,10 @@ export const serverSchema = z.object({ GOOGLE_CLIENT_ID: z.string().optional(), GOOGLE_CLIENT_SECRET: z.string().optional(), HASHING_SECRET: z.string().min(1), + ENABLE_GITHUB_APP: z.boolean().optional(), + GITHUB_APP_ID: z.string().optional(), + GITHUB_APP_PRIVATE_KEY: z.string().optional(), + OPENAI_API_KEY: z.string().optional(), }); /** diff --git a/apps/web/src/pages/projects/[projectId]/settings.tsx b/apps/web/src/pages/projects/[projectId]/settings.tsx index 0c526bae..6a1cf2fe 100644 --- a/apps/web/src/pages/projects/[projectId]/settings.tsx +++ b/apps/web/src/pages/projects/[projectId]/settings.tsx @@ -1,4 +1,5 @@ import { ROLE, type User } from "@prisma/client"; +import { GitHubLogoIcon } from "@radix-ui/react-icons"; import clsx from "clsx"; import { DashboardButton } from "components/DashboardButton"; import { @@ -11,6 +12,7 @@ import { Layout } from "components/Layout"; import { FullPageLoadingSpinner } from "components/LoadingSpinner"; import { Progress } from "components/Progress"; import { RemoveUserModal } from "components/RemoveUserModal"; +import { Integrations } from "components/settings/Integrations"; import { Button } from "components/ui/button"; import { Input } from "components/ui/input"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "components/ui/tabs"; @@ -33,6 +35,7 @@ const SETTINGS_TABS = { General: "general", Team: "team", Billing: "billing", + Integrations: "integrations", Danger: "danger", } as const; @@ -47,6 +50,7 @@ const SettingsPage: NextPageWithLayout = () => { SETTINGS_TABS.Team, SETTINGS_TABS.Billing, SETTINGS_TABS.Danger, + SETTINGS_TABS.Integrations, ] as const).withDefault(SETTINGS_TABS.General) ); const router = useRouter(); @@ -56,9 +60,14 @@ const SettingsPage: NextPageWithLayout = () => { const projectNameRef = useRef<HTMLInputElement>(null); const trpcContext = trpc.useContext(); - const { data, isLoading, isError } = trpc.project.getProjectData.useQuery({ - projectId, - }); + const { data, isLoading, isError } = trpc.project.getProjectData.useQuery( + { + projectId, + }, + { + enabled: !!projectId, + } + ); const session = useSession(); @@ -225,6 +234,33 @@ const SettingsPage: NextPageWithLayout = () => { </div> </DashboardSection> </TabsContent> + <TabsContent value="integrations"> + <DashboardSection> + <DashboardSectionTitle>Integrations</DashboardSectionTitle> + <DashboardSectionSubtitle className="mb-8"> + Manage your integrations + </DashboardSectionSubtitle> + <div className="flex flex-col space-y-4"> + {data.project.integrations.length === 0 ? ( + <div className="flex flex-col space-y-3"> + <Link + href={`/api/integrations/github?projectId=${projectId}`} + > + <Button + variant="outline" + className="flex space-x-2 mr-auto" + > + <GitHubLogoIcon height={18} width={18} />{" "} + <span>Connect with Github</span> + </Button> + </Link> + </div> + ) : ( + <Integrations projectId={projectId} /> + )} + </div> + </DashboardSection> + </TabsContent> <TabsContent value="billing"> <DashboardSection> <DashboardSectionTitle>Usage</DashboardSectionTitle> diff --git a/apps/web/src/server/common/auth.ts b/apps/web/src/server/common/auth.ts new file mode 100644 index 00000000..6698b3d3 --- /dev/null +++ b/apps/web/src/server/common/auth.ts @@ -0,0 +1,22 @@ +import { TRPCError } from "@trpc/server"; +import { prisma } from "server/db/client"; + +export async function assertUserHasAcessToProject( + projectId: string, + userId: string +) { + const project = await prisma.project.findFirst({ + where: { + id: projectId, + users: { + some: { + userId: userId, + }, + }, + }, + }); + if (!project) { + throw new TRPCError({ code: "UNAUTHORIZED" }); + } + return project; +} diff --git a/apps/web/src/server/common/github-app.ts b/apps/web/src/server/common/github-app.ts new file mode 100644 index 00000000..915dc0c2 --- /dev/null +++ b/apps/web/src/server/common/github-app.ts @@ -0,0 +1,50 @@ +import memoize from "memoize"; +import { App } from "octokit"; +import { env } from "../../env/server.mjs"; + +if ( + env.ENABLE_GITHUB_APP && + (!env.GITHUB_APP_ID || !env.GITHUB_APP_PRIVATE_KEY) +) { + throw new Error("Missing required environment variables for GitHub App"); +} + +export const githubApp = new App({ + // biome-ignore lint/style/noNonNullAssertion: we check above + appId: env.GITHUB_APP_ID!, + // biome-ignore lint/style/noNonNullAssertion: we check above + privateKey: env.GITHUB_APP_PRIVATE_KEY!, +}); + +const PER_PAGE = 100; +export const getAllRepositoriesForInstallation = memoize( + async (installationId: number) => { + const gh = await githubApp.getInstallationOctokit(installationId); + let count = 0; + const res = await gh.request("GET /installation/repositories", { + installation_id: installationId, + per_page: 100, + }); + count += res.data.repositories.length; + const repositories = res.data.repositories; + + let hasMore = res.data.total_count > count; + while (hasMore) { + const res = await gh.request("GET /installation/repositories", { + installation_id: installationId, + per_page: 100, + page: Math.ceil(count / PER_PAGE) + 1, + }); + count += res.data.repositories.length; + hasMore = res.data.total_count > count; + repositories.push(...res.data.repositories); + } + + return repositories.toSorted((a, b) => { + return a.full_name.localeCompare(b.full_name); + }); + }, + { + maxAge: 1000 * 60, + } +); diff --git a/apps/web/src/server/common/integrations.ts b/apps/web/src/server/common/integrations.ts new file mode 100644 index 00000000..9eb38e77 --- /dev/null +++ b/apps/web/src/server/common/integrations.ts @@ -0,0 +1,12 @@ +import { z } from "zod"; + +export const githubIntegrationSettingsSchema = z + .object({ + installationId: z.number(), + repositoryIds: z.array(z.number()), + }) + .strict(); + +export type GithubIntegrationSettings = z.infer< + typeof githubIntegrationSettingsSchema +>; diff --git a/apps/web/src/server/services/AiFlagRemovalService.ts b/apps/web/src/server/services/AiFlagRemovalService.ts new file mode 100644 index 00000000..134cc87d --- /dev/null +++ b/apps/web/src/server/services/AiFlagRemovalService.ts @@ -0,0 +1,32 @@ +import type { OpenAI } from "openai"; + +export class AIFlagRemovalService { + constructor(private openai: OpenAI) {} + + async removeFlagFromCode(code: string, flagName: string) { + const response = await this.openai.chat.completions.create({ + model: "gpt-4o-mini", + messages: [ + { + role: "system", + content: `You are an expert in code refactoring and have been assigned to update a codebase to remove calls to the useFeatureFlag hook where the parameter is ${flagName}. Your task is to ensure that all code paths that depend on the truthiness of this hook are always executed. But don't just replace the result of the function with true but rather remove the variable (if used). and update the code as if there was never a flag that prevented certain paths. You should only include the code without any other information or formatting. You should never update any other code that isnt related to the flag with the name ${flagName}. You should keep the formatting and line ending of the original code.`, + }, + { + role: "user", + content: [ + { + type: "text", + text: code, + }, + ], + }, + ], + temperature: 1, + max_tokens: 16383, + top_p: 1, + frequency_penalty: 0, + presence_penalty: 0, + }); + return response.choices[0]?.message.content; + } +} diff --git a/apps/web/src/server/services/IntegrationService.ts b/apps/web/src/server/services/IntegrationService.ts new file mode 100644 index 00000000..a96d2085 --- /dev/null +++ b/apps/web/src/server/services/IntegrationService.ts @@ -0,0 +1,3 @@ +export namespace IntegrationService { + // createIntegration(projectId: string, In) +} diff --git a/apps/web/src/server/trpc/router/flags.ts b/apps/web/src/server/trpc/router/flags.ts index d0ffdb06..8b694c31 100644 --- a/apps/web/src/server/trpc/router/flags.ts +++ b/apps/web/src/server/trpc/router/flags.ts @@ -1,6 +1,13 @@ +import { randomUUID } from "node:crypto"; import { FeatureFlagType } from "@prisma/client"; import { TRPCError } from "@trpc/server"; +import { getUseFeatureFlagRegex } from "@tryabby/core"; +import { env } from "env/server.mjs"; +import OpenAI from "openai"; import { ConfigCache } from "server/common/config-cache"; +import { githubApp } from "server/common/github-app"; +import { githubIntegrationSettingsSchema } from "server/common/integrations"; +import { AIFlagRemovalService } from "server/services/AiFlagRemovalService"; import { FlagService } from "server/services/FlagService"; import { validateFlag } from "utils/validateFlags"; import { z } from "zod"; @@ -15,40 +22,57 @@ export const flagRouter = router({ }) ) .query(async ({ ctx, input }) => { - const flags = await ctx.prisma.featureFlag.findMany({ - where: { - type: { - in: input.types, - }, - project: { - id: input.projectId, - users: { - some: { - userId: ctx.session.user.id, - }, - }, - }, - }, - orderBy: { createdAt: "asc" }, + const project = await ctx.prisma.project.findUnique({ + where: { id: input.projectId }, include: { - values: { include: { environment: true } }, + users: { select: { userId: true } }, + integrations: { where: { type: "GITHUB" } }, }, }); - - const environments = await ctx.prisma.environment.findMany({ - where: { - project: { - id: input.projectId, - users: { - some: { - userId: ctx.session.user.id, + if (!project) throw new TRPCError({ code: "NOT_FOUND" }); + if (!project.users.some((u) => u.userId === ctx.session.user.id)) { + throw new TRPCError({ code: "UNAUTHORIZED" }); + } + const [flags, environments] = await Promise.all([ + ctx.prisma.featureFlag.findMany({ + where: { + type: { + in: input.types, + }, + project: { + id: input.projectId, + }, + }, + orderBy: { createdAt: "asc" }, + include: { + values: { + include: { + environment: true, }, }, }, - }, - orderBy: { sortIndex: "asc" }, - }); - return { flags, environments }; + }), + ctx.prisma.environment.findMany({ + where: { + project: { + id: input.projectId, + }, + }, + orderBy: { sortIndex: "asc" }, + }), + ]); + const githubIntegration = project.integrations[0]; + const integrationSettings = githubIntegration + ? githubIntegrationSettingsSchema.parse(githubIntegration.settings) + : null; + + return { + flags, + environments, + hasGithubIntegration: + !!integrationSettings?.repositoryIds[0] && + integrationSettings?.installationId, + }; }), addFlag: protectedProcedure .input( @@ -300,4 +324,159 @@ export const flagRouter = router({ }) ); }), + createFlagRemovalPR: protectedProcedure + .input( + z.object({ + flagId: z.string(), + }) + ) + .mutation(async ({ ctx, input }) => { + const flag = await ctx.prisma.featureFlag.findUnique({ + where: { id: input.flagId }, + include: { + project: { + include: { users: true, integrations: true }, + }, + }, + }); + if (!flag) throw new TRPCError({ code: "NOT_FOUND" }); + const integration = flag.project.integrations.find( + (i) => i.type === "GITHUB" + ); + if (!integration) { + throw new TRPCError({ code: "INTERNAL_SERVER_ERROR" }); + } + + if (!flag.project.users.some((u) => u.userId === ctx.session.user.id)) { + throw new TRPCError({ code: "UNAUTHORIZED" }); + } + + const parsedIntegration = githubIntegrationSettingsSchema.parse( + integration.settings + ); + const gh = await githubApp.getInstallationOctokit( + parsedIntegration.installationId + ); + const repositoryId = parsedIntegration.repositoryIds[0]; + if (!repositoryId) { + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + }); + } + + const repo = await gh.request("GET /repositories/:id", { + id: repositoryId, + }); + + if (!repo) { + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + }); + } + + if (!env.OPENAI_API_KEY) { + throw new TRPCError({ code: "INTERNAL_SERVER_ERROR" }); + } + const ai = new AIFlagRemovalService( + new OpenAI({ + apiKey: env.OPENAI_API_KEY, + }) + ); + + const flagRegex = getUseFeatureFlagRegex(flag.name); + const owner = repo.data.owner.login; + const name = repo.data.name; + const defaultBranch = repo.data.default_branch; + + const u = await gh.request( + "GET /repos/{owner}/{repo}/git/trees/{tree_sha}", + { + owner, + repo: name, + tree_sha: defaultBranch, + recursive: "1", + } + ); + + const files = u.data.tree + .filter((f) => f.type === "blob") + .filter((f) => f.path?.endsWith(".ts") || f.path?.endsWith(".tsx")); + + if (files.length === 0) { + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: "No files found in the repository", + }); + } + + const fileContents = ( + await Promise.all( + files.flatMap(async (f) => { + if (!f.sha || !f.path) return []; + const res = await gh.request( + "GET /repos/{owner}/{repo}/git/blobs/{file_sha}", + { + owner, + repo: name, + file_sha: f.sha, + } + ); + const fileContent = Buffer.from( + res.data.content, + "base64" + ).toString("utf-8"); + if (!flagRegex.test(fileContent)) return []; + return { + fileSha: f.sha, + filePath: f.path, + fileContent, + }; + }) + ) + ).flat(); + const baseBranchResponse = await gh.rest.git.getRef({ + owner, + repo: name, + ref: `heads/${defaultBranch}`, + }); + + const baseSha = baseBranchResponse.data.object.sha; + + const b = await gh.rest.git.createRef({ + owner: "cstrnt", + repo: name, + ref: `refs/heads/abby_${randomUUID()}_remove_client_flag`, + sha: baseSha, + }); + + await Promise.all( + fileContents.map(async (f) => { + const updatedCode = await ai.removeFlagFromCode( + f.fileContent, + flag.name + ); + if (!updatedCode) return; + return gh.rest.repos.createOrUpdateFileContents({ + owner, + repo: name, + path: f.filePath, + message: `Remove Feature Flag: ${flag.name}`, + content: Buffer.from(updatedCode).toString("base64"), + sha: f.fileSha, + branch: b.data.ref, + }); + }) + ); + + const response = await gh.rest.pulls.create({ + owner, + repo: name, + title: `[ABBY] Remove Feature Flag ${flag.name}`, + head: b.data.ref, + base: defaultBranch, + body: `This PR was created by Abby to remove the flag ${flag.name} from the codebase. Please review the changes and merge when ready.`, + }); + + return response.data.html_url; + }), }); diff --git a/apps/web/src/server/trpc/router/project.ts b/apps/web/src/server/trpc/router/project.ts index cdfee39a..0573d3ac 100644 --- a/apps/web/src/server/trpc/router/project.ts +++ b/apps/web/src/server/trpc/router/project.ts @@ -2,6 +2,7 @@ import { type Option, ROLE } from "@prisma/client"; import { TRPCError } from "@trpc/server"; import { PLANS, planNameSchema } from "server/common/plans"; import { stripe } from "server/common/stripe"; +import { prisma } from "server/db/client"; import { EventService } from "server/services/EventService"; import { ProjectService } from "server/services/ProjectService"; import { generateCodeSnippets } from "utils/snippets"; @@ -12,6 +13,13 @@ export type ClientOption = Omit<Option, "chance"> & { }; import { AbbyEventType } from "@tryabby/core"; import dayjs from "dayjs"; +import { assertUserHasAcessToProject } from "server/common/auth"; +import { + getAllRepositoriesForInstallation, + githubApp, +} from "server/common/github-app"; +import { githubIntegrationSettingsSchema } from "server/common/integrations"; +import { match } from "ts-pattern"; import { updateProjectsOnSession } from "utils/updateSession"; import { protectedProcedure, router } from "../trpc"; @@ -37,6 +45,7 @@ export const projectRouter = router({ environments: true, featureFlags: true, users: { include: { user: true } }, + integrations: true, }, }); @@ -314,4 +323,96 @@ export const projectRouter = router({ nextCursor, }; }), + getIntegrations: protectedProcedure + .input( + z.object({ + projectId: z.string(), + }) + ) + .query(async ({ ctx, input }) => { + await assertUserHasAcessToProject(input.projectId, ctx.session.user.id); + + const project = await prisma.project.findUniqueOrThrow({ + where: { id: input.projectId }, + include: { integrations: true }, + }); + + return await Promise.all( + project.integrations.map((i) => + match(i.type) + .with("GITHUB", async () => { + const integration = + await githubIntegrationSettingsSchema.parseAsync(i.settings); + + const gh = await githubApp.getInstallationOctokit( + integration.installationId + ); + const [potentialRepositories, ...installedRepos] = + await Promise.all([ + getAllRepositoriesForInstallation(integration.installationId), + ...integration.repositoryIds.map((id) => + gh.request("GET /repositories/:id", { + id, + }) + ), + ]); + + return { + ...i, + settings: integration, + potentialRepositories: potentialRepositories.map((r) => ({ + name: r.name, + owner: r.owner.login, + id: r.id, + })), + installedRepos: installedRepos.map((r) => ({ + name: r.data.name, + owner: r.data.owner.login, + id: r.data.id, + })), + }; + }) + .exhaustive() + ) + ); + }), + updateGithubIntegration: protectedProcedure + .input( + z.object({ + integrationId: z.string(), + repositoryId: z.number(), + }) + ) + .mutation(async ({ ctx, input }) => { + const integration = await prisma.integration.findUnique({ + where: { + id: input.integrationId, + }, + include: { project: { include: { users: true } } }, + }); + if (!integration) throw new TRPCError({ code: "NOT_FOUND" }); + if (integration.type !== "GITHUB") { + throw new TRPCError({ code: "INTERNAL_SERVER_ERROR" }); + } + if ( + !integration.project.users.some((u) => u.userId === ctx.session.user.id) + ) { + throw new TRPCError({ code: "UNAUTHORIZED" }); + } + + const newSettings = await githubIntegrationSettingsSchema.parseAsync( + integration.settings + ); + newSettings.repositoryIds = [input.repositoryId]; + + return await prisma.integration.update({ + where: { + id: input.integrationId, + }, + data: { + settings: + await githubIntegrationSettingsSchema.parseAsync(newSettings), + }, + }); + }), }); diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json index 7210a3f3..3314f645 100644 --- a/apps/web/tsconfig.json +++ b/apps/web/tsconfig.json @@ -10,7 +10,7 @@ "esModuleInterop": true, "allowSyntheticDefaultImports": true, "module": "esnext", - "moduleResolution": "node", + "moduleResolution": "Bundler", "resolveJsonModule": true, "isolatedModules": true, "jsx": "preserve", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8d3f486c..2bdbd367 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -117,13 +117,13 @@ importers: version: 4.5.1(monaco-editor@0.39.0)(react-dom@18.2.0)(react@18.2.0) '@next-auth/prisma-adapter': specifier: 1.0.5 - version: 1.0.5(@prisma/client@5.6.0)(next-auth@4.22.1) + version: 1.0.5(@prisma/client@5.18.0)(next-auth@4.22.1) '@next/mdx': specifier: 14.0.4 version: 14.0.4(@mdx-js/loader@3.0.0)(@mdx-js/react@3.0.0) '@prisma/client': - specifier: 5.6.0 - version: 5.6.0(prisma@5.6.0) + specifier: 5.18.0 + version: 5.18.0(prisma@5.18.0) '@radix-ui/react-avatar': specifier: ^1.0.3 version: 1.0.3(@types/react-dom@18.0.5)(@types/react@18.0.14)(react-dom@18.2.0)(react@18.2.0) @@ -292,6 +292,9 @@ importers: lucide-react: specifier: 0.320.0 version: 0.320.0(react@18.2.0) + memoize: + specifier: ^10.0.0 + version: 10.0.0 micro: specifier: ^10.0.1 version: 10.0.1 @@ -326,8 +329,14 @@ importers: specifier: ^1.17.8 version: 1.17.8(next@14.1.1) octokit: - specifier: ^2.0.18 - version: 2.0.19 + specifier: ^4.0.2 + version: 4.0.2 + openai: + specifier: ^4.56.0 + version: 4.56.0(zod@3.21.4) + prettier: + specifier: ^2.8.7 + version: 2.8.8 rate-limiter-flexible: specifier: ^5.0.3 version: 5.0.3 @@ -428,15 +437,12 @@ importers: postcss: specifier: ^8.4.21 version: 8.4.24 - prettier: - specifier: ^2.8.7 - version: 2.8.8 prettier-plugin-tailwindcss: specifier: ^0.1.13 version: 0.1.13(prettier@2.8.8) prisma: - specifier: 5.6.0 - version: 5.6.0 + specifier: 5.18.0 + version: 5.18.0 tailwindcss: specifier: ^3.3.1 version: 3.3.2(ts-node@10.9.1) @@ -7872,13 +7878,13 @@ packages: tar-fs: 2.1.1 dev: true - /@next-auth/prisma-adapter@1.0.5(@prisma/client@5.6.0)(next-auth@4.22.1): + /@next-auth/prisma-adapter@1.0.5(@prisma/client@5.18.0)(next-auth@4.22.1): resolution: {integrity: sha512-VqMS11IxPXrPGXw6Oul6jcyS/n8GLOWzRMrPr3EMdtD6eOalM6zz05j08PcNiis8QzkfuYnCv49OvufTuaEwYQ==} peerDependencies: '@prisma/client': '>=2.26.0 || >=3' next-auth: ^4 dependencies: - '@prisma/client': 5.6.0(prisma@5.6.0) + '@prisma/client': 5.18.0(prisma@5.18.0) next-auth: 4.22.1(next@14.1.1)(nodemailer@6.9.3)(react-dom@18.2.0)(react@18.2.0) dev: false @@ -8205,259 +8211,232 @@ packages: - supports-color dev: true - /@octokit/app@13.1.5: - resolution: {integrity: sha512-6qTa24S+gdQUU66SCVfqTkyt2jAr9/ZeyPqJhnNI9PZ8Wum4lQy3bPS+voGlxABNOlzRKnxbSdYKoraMr3MqBA==} - engines: {node: '>= 14'} + /@octokit/app@15.1.0: + resolution: {integrity: sha512-TkBr7QgOmE6ORxvIAhDbZsqPkF7RSqTY4pLTtUQCvr6dTXqvi2fFo46q3h1lxlk/sGMQjqyZ0kEahkD/NyzOHg==} + engines: {node: '>= 18'} dependencies: - '@octokit/auth-app': 4.0.13 - '@octokit/auth-unauthenticated': 3.0.5 - '@octokit/core': 4.2.1 - '@octokit/oauth-app': 4.2.2 - '@octokit/plugin-paginate-rest': 6.1.2(@octokit/core@4.2.1) - '@octokit/types': 9.3.1 - '@octokit/webhooks': 10.9.1 - transitivePeerDependencies: - - encoding + '@octokit/auth-app': 7.1.0 + '@octokit/auth-unauthenticated': 6.1.0 + '@octokit/core': 6.1.2 + '@octokit/oauth-app': 7.1.3 + '@octokit/plugin-paginate-rest': 11.3.3(@octokit/core@6.1.2) + '@octokit/types': 13.5.0 + '@octokit/webhooks': 13.3.0 dev: false - /@octokit/auth-app@4.0.13: - resolution: {integrity: sha512-NBQkmR/Zsc+8fWcVIFrwDgNXS7f4XDrkd9LHdi9DPQw1NdGHLviLzRO2ZBwTtepnwHXW5VTrVU9eFGijMUqllg==} - engines: {node: '>= 14'} + /@octokit/auth-app@7.1.0: + resolution: {integrity: sha512-cazGaJPSgeZ8NkVYeM/C5l/6IQ5vZnsI8p1aMucadCkt/bndI+q+VqwrlnWbASRmenjOkf1t1RpCKrif53U8gw==} + engines: {node: '>= 18'} dependencies: - '@octokit/auth-oauth-app': 5.0.6 - '@octokit/auth-oauth-user': 2.1.2 - '@octokit/request': 6.2.5 - '@octokit/request-error': 3.0.3 - '@octokit/types': 9.3.1 - deprecation: 2.3.1 - lru-cache: 9.1.2 - universal-github-app-jwt: 1.1.1 - universal-user-agent: 6.0.0 - transitivePeerDependencies: - - encoding + '@octokit/auth-oauth-app': 8.1.1 + '@octokit/auth-oauth-user': 5.1.1 + '@octokit/request': 9.1.3 + '@octokit/request-error': 6.1.4 + '@octokit/types': 13.5.0 + lru-cache: 10.4.3 + universal-github-app-jwt: 2.2.0 + universal-user-agent: 7.0.2 dev: false - /@octokit/auth-oauth-app@5.0.6: - resolution: {integrity: sha512-SxyfIBfeFcWd9Z/m1xa4LENTQ3l1y6Nrg31k2Dcb1jS5ov7pmwMJZ6OGX8q3K9slRgVpeAjNA1ipOAMHkieqyw==} - engines: {node: '>= 14'} + /@octokit/auth-oauth-app@8.1.1: + resolution: {integrity: sha512-5UtmxXAvU2wfcHIPPDWzVSAWXVJzG3NWsxb7zCFplCWEmMCArSZV0UQu5jw5goLQXbFyOr5onzEH37UJB3zQQg==} + engines: {node: '>= 18'} dependencies: - '@octokit/auth-oauth-device': 4.0.5 - '@octokit/auth-oauth-user': 2.1.2 - '@octokit/request': 6.2.5 - '@octokit/types': 9.3.1 - '@types/btoa-lite': 1.0.0 - btoa-lite: 1.0.0 - universal-user-agent: 6.0.0 - transitivePeerDependencies: - - encoding + '@octokit/auth-oauth-device': 7.1.1 + '@octokit/auth-oauth-user': 5.1.1 + '@octokit/request': 9.1.3 + '@octokit/types': 13.5.0 + universal-user-agent: 7.0.2 dev: false - /@octokit/auth-oauth-device@4.0.5: - resolution: {integrity: sha512-XyhoWRTzf2ZX0aZ52a6Ew5S5VBAfwwx1QnC2Np6Et3MWQpZjlREIcbcvVZtkNuXp6Z9EeiSLSDUqm3C+aMEHzQ==} - engines: {node: '>= 14'} + /@octokit/auth-oauth-device@7.1.1: + resolution: {integrity: sha512-HWl8lYueHonuyjrKKIup/1tiy0xcmQCdq5ikvMO1YwkNNkxb6DXfrPjrMYItNLyCP/o2H87WuijuE+SlBTT8eg==} + engines: {node: '>= 18'} dependencies: - '@octokit/oauth-methods': 2.0.6 - '@octokit/request': 6.2.5 - '@octokit/types': 9.3.1 - universal-user-agent: 6.0.0 - transitivePeerDependencies: - - encoding + '@octokit/oauth-methods': 5.1.2 + '@octokit/request': 9.1.3 + '@octokit/types': 13.5.0 + universal-user-agent: 7.0.2 dev: false - /@octokit/auth-oauth-user@2.1.2: - resolution: {integrity: sha512-kkRqNmFe7s5GQcojE3nSlF+AzYPpPv7kvP/xYEnE57584pixaFBH8Vovt+w5Y3E4zWUEOxjdLItmBTFAWECPAg==} - engines: {node: '>= 14'} + /@octokit/auth-oauth-user@5.1.1: + resolution: {integrity: sha512-rRkMz0ErOppdvEfnemHJXgZ9vTPhBuC6yASeFaB7I2yLMd7QpjfrL1mnvRPlyKo+M6eeLxrKanXJ9Qte29SRsw==} + engines: {node: '>= 18'} dependencies: - '@octokit/auth-oauth-device': 4.0.5 - '@octokit/oauth-methods': 2.0.6 - '@octokit/request': 6.2.5 - '@octokit/types': 9.3.1 - btoa-lite: 1.0.0 - universal-user-agent: 6.0.0 - transitivePeerDependencies: - - encoding + '@octokit/auth-oauth-device': 7.1.1 + '@octokit/oauth-methods': 5.1.2 + '@octokit/request': 9.1.3 + '@octokit/types': 13.5.0 + universal-user-agent: 7.0.2 dev: false - /@octokit/auth-token@3.0.4: - resolution: {integrity: sha512-TWFX7cZF2LXoCvdmJWY7XVPi74aSY0+FfBZNSXEXFkMpjcqsQwDSYVv5FhRFaI0V1ECnwbz4j59T/G+rXNWaIQ==} - engines: {node: '>= 14'} + /@octokit/auth-token@5.1.1: + resolution: {integrity: sha512-rh3G3wDO8J9wSjfI436JUKzHIxq8NaiL0tVeB2aXmG6p/9859aUOAjA9pmSPNGGZxfwmaJ9ozOJImuNVJdpvbA==} + engines: {node: '>= 18'} dev: false - /@octokit/auth-unauthenticated@3.0.5: - resolution: {integrity: sha512-yH2GPFcjrTvDWPwJWWCh0tPPtTL5SMgivgKPA+6v/XmYN6hGQkAto8JtZibSKOpf8ipmeYhLNWQ2UgW0GYILCw==} - engines: {node: '>= 14'} + /@octokit/auth-unauthenticated@6.1.0: + resolution: {integrity: sha512-zPSmfrUAcspZH/lOFQnVnvjQZsIvmfApQH6GzJrkIunDooU1Su2qt2FfMTSVPRp7WLTQyC20Kd55lF+mIYaohQ==} + engines: {node: '>= 18'} dependencies: - '@octokit/request-error': 3.0.3 - '@octokit/types': 9.3.1 + '@octokit/request-error': 6.1.4 + '@octokit/types': 13.5.0 dev: false - /@octokit/core@4.2.1: - resolution: {integrity: sha512-tEDxFx8E38zF3gT7sSMDrT1tGumDgsw5yPG6BBh/X+5ClIQfMH/Yqocxz1PnHx6CHyF6pxmovUTOfZAUvQ0Lvw==} - engines: {node: '>= 14'} + /@octokit/core@6.1.2: + resolution: {integrity: sha512-hEb7Ma4cGJGEUNOAVmyfdB/3WirWMg5hDuNFVejGEDFqupeOysLc2sG6HJxY2etBp5YQu5Wtxwi020jS9xlUwg==} + engines: {node: '>= 18'} dependencies: - '@octokit/auth-token': 3.0.4 - '@octokit/graphql': 5.0.6 - '@octokit/request': 6.2.5 - '@octokit/request-error': 3.0.3 - '@octokit/types': 9.3.1 - before-after-hook: 2.2.3 - universal-user-agent: 6.0.0 - transitivePeerDependencies: - - encoding + '@octokit/auth-token': 5.1.1 + '@octokit/graphql': 8.1.1 + '@octokit/request': 9.1.3 + '@octokit/request-error': 6.1.4 + '@octokit/types': 13.5.0 + before-after-hook: 3.0.2 + universal-user-agent: 7.0.2 dev: false - /@octokit/endpoint@7.0.6: - resolution: {integrity: sha512-5L4fseVRUsDFGR00tMWD/Trdeeihn999rTMGRMC1G/Ldi1uWlWJzI98H4Iak5DB/RVvQuyMYKqSK/R6mbSOQyg==} - engines: {node: '>= 14'} + /@octokit/endpoint@10.1.1: + resolution: {integrity: sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==} + engines: {node: '>= 18'} dependencies: - '@octokit/types': 9.3.1 - is-plain-object: 5.0.0 - universal-user-agent: 6.0.0 + '@octokit/types': 13.5.0 + universal-user-agent: 7.0.2 dev: false - /@octokit/graphql@5.0.6: - resolution: {integrity: sha512-Fxyxdy/JH0MnIB5h+UQ3yCoh1FG4kWXfFKkpWqjZHw/p+Kc8Y44Hu/kCgNBT6nU1shNumEchmW/sUO1JuQnPcw==} - engines: {node: '>= 14'} + /@octokit/graphql@8.1.1: + resolution: {integrity: sha512-ukiRmuHTi6ebQx/HFRCXKbDlOh/7xEV6QUXaE7MJEKGNAncGI/STSbOkl12qVXZrfZdpXctx5O9X1AIaebiDBg==} + engines: {node: '>= 18'} dependencies: - '@octokit/request': 6.2.5 - '@octokit/types': 9.3.1 - universal-user-agent: 6.0.0 - transitivePeerDependencies: - - encoding + '@octokit/request': 9.1.3 + '@octokit/types': 13.5.0 + universal-user-agent: 7.0.2 dev: false - /@octokit/oauth-app@4.2.2: - resolution: {integrity: sha512-/jsPd43Yu2UXJ4XGq9KyOjPj5kNWQ5pfVzeDEfIVE8ENchyIPS+/IY2a8b0+OQSAsBKBLTHVp9m51RfGHmPZlw==} - engines: {node: '>= 14'} + /@octokit/oauth-app@7.1.3: + resolution: {integrity: sha512-EHXbOpBkSGVVGF1W+NLMmsnSsJRkcrnVmDKt0TQYRBb6xWfWzoi9sBD4DIqZ8jGhOWO/V8t4fqFyJ4vDQDn9bg==} + engines: {node: '>= 18'} dependencies: - '@octokit/auth-oauth-app': 5.0.6 - '@octokit/auth-oauth-user': 2.1.2 - '@octokit/auth-unauthenticated': 3.0.5 - '@octokit/core': 4.2.1 - '@octokit/oauth-authorization-url': 5.0.0 - '@octokit/oauth-methods': 2.0.6 + '@octokit/auth-oauth-app': 8.1.1 + '@octokit/auth-oauth-user': 5.1.1 + '@octokit/auth-unauthenticated': 6.1.0 + '@octokit/core': 6.1.2 + '@octokit/oauth-authorization-url': 7.1.1 + '@octokit/oauth-methods': 5.1.2 '@types/aws-lambda': 8.10.117 - fromentries: 1.3.2 - universal-user-agent: 6.0.0 - transitivePeerDependencies: - - encoding + universal-user-agent: 7.0.2 dev: false - /@octokit/oauth-authorization-url@5.0.0: - resolution: {integrity: sha512-y1WhN+ERDZTh0qZ4SR+zotgsQUE1ysKnvBt1hvDRB2WRzYtVKQjn97HEPzoehh66Fj9LwNdlZh+p6TJatT0zzg==} - engines: {node: '>= 14'} + /@octokit/oauth-authorization-url@7.1.1: + resolution: {integrity: sha512-ooXV8GBSabSWyhLUowlMIVd9l1s2nsOGQdlP2SQ4LnkEsGXzeCvbSbCPdZThXhEFzleGPwbapT0Sb+YhXRyjCA==} + engines: {node: '>= 18'} dev: false - /@octokit/oauth-methods@2.0.6: - resolution: {integrity: sha512-l9Uml2iGN2aTWLZcm8hV+neBiFXAQ9+3sKiQe/sgumHlL6HDg0AQ8/l16xX/5jJvfxueqTW5CWbzd0MjnlfHZw==} - engines: {node: '>= 14'} + /@octokit/oauth-methods@5.1.2: + resolution: {integrity: sha512-C5lglRD+sBlbrhCUTxgJAFjWgJlmTx5bQ7Ch0+2uqRjYv7Cfb5xpX4WuSC9UgQna3sqRGBL9EImX9PvTpMaQ7g==} + engines: {node: '>= 18'} dependencies: - '@octokit/oauth-authorization-url': 5.0.0 - '@octokit/request': 6.2.5 - '@octokit/request-error': 3.0.3 - '@octokit/types': 9.3.1 - btoa-lite: 1.0.0 - transitivePeerDependencies: - - encoding + '@octokit/oauth-authorization-url': 7.1.1 + '@octokit/request': 9.1.3 + '@octokit/request-error': 6.1.4 + '@octokit/types': 13.5.0 dev: false - /@octokit/openapi-types@18.0.0: - resolution: {integrity: sha512-V8GImKs3TeQRxRtXFpG2wl19V7444NIOTDF24AWuIbmNaNYOQMWRbjcGDXV5B+0n887fgDcuMNOmlul+k+oJtw==} + /@octokit/openapi-types@22.2.0: + resolution: {integrity: sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==} dev: false - /@octokit/plugin-paginate-rest@6.1.2(@octokit/core@4.2.1): - resolution: {integrity: sha512-qhrmtQeHU/IivxucOV1bbI/xZyC/iOBhclokv7Sut5vnejAIAEXVcGQeRpQlU39E0WwK9lNvJHphHri/DB6lbQ==} - engines: {node: '>= 14'} + /@octokit/openapi-webhooks-types@8.3.0: + resolution: {integrity: sha512-vKLsoR4xQxg4Z+6rU/F65ItTUz/EXbD+j/d4mlq2GW8TsA4Tc8Kdma2JTAAJ5hrKWUQzkR/Esn2fjsqiVRYaQg==} + dev: false + + /@octokit/plugin-paginate-graphql@5.2.2(@octokit/core@6.1.2): + resolution: {integrity: sha512-7znSVvlNAOJisCqAnjN1FtEziweOHSjPGAuc5W58NeGNAr/ZB57yCsjQbXDlWsVryA7hHQaEQPcBbJYFawlkyg==} + engines: {node: '>= 18'} peerDependencies: - '@octokit/core': '>=4' + '@octokit/core': '>=6' dependencies: - '@octokit/core': 4.2.1 - '@octokit/tsconfig': 1.0.2 - '@octokit/types': 9.3.1 + '@octokit/core': 6.1.2 dev: false - /@octokit/plugin-rest-endpoint-methods@7.2.1(@octokit/core@4.2.1): - resolution: {integrity: sha512-UmlNrrcF+AXxcxhZslTt1a/8aDxUKH0trrt/mJCxEPrWbW1ZEc+6xxcd5/n0iw3b+Xo8UBJQUKDr71+vNCBpRQ==} - engines: {node: '>= 14'} + /@octokit/plugin-paginate-rest@11.3.3(@octokit/core@6.1.2): + resolution: {integrity: sha512-o4WRoOJZlKqEEgj+i9CpcmnByvtzoUYC6I8PD2SA95M+BJ2x8h7oLcVOg9qcowWXBOdcTRsMZiwvM3EyLm9AfA==} + engines: {node: '>= 18'} peerDependencies: - '@octokit/core': '>=3' + '@octokit/core': '>=6' dependencies: - '@octokit/core': 4.2.1 - '@octokit/types': 9.3.1 + '@octokit/core': 6.1.2 + '@octokit/types': 13.5.0 dev: false - /@octokit/plugin-retry@4.1.6(@octokit/core@4.2.1): - resolution: {integrity: sha512-obkYzIgEC75r8+9Pnfiiqy3y/x1bc3QLE5B7qvv9wi9Kj0R5tGQFC6QMBg1154WQ9lAVypuQDGyp3hNpp15gQQ==} - engines: {node: '>= 14'} + /@octokit/plugin-rest-endpoint-methods@13.2.4(@octokit/core@6.1.2): + resolution: {integrity: sha512-gusyAVgTrPiuXOdfqOySMDztQHv6928PQ3E4dqVGEtOvRXAKRbJR4b1zQyniIT9waqaWk/UDaoJ2dyPr7Bk7Iw==} + engines: {node: '>= 18'} peerDependencies: - '@octokit/core': '>=3' + '@octokit/core': '>=6' dependencies: - '@octokit/core': 4.2.1 - '@octokit/types': 9.3.1 - bottleneck: 2.19.5 + '@octokit/core': 6.1.2 + '@octokit/types': 13.5.0 dev: false - /@octokit/plugin-throttling@5.2.3(@octokit/core@4.2.1): - resolution: {integrity: sha512-C9CFg9mrf6cugneKiaI841iG8DOv6P5XXkjmiNNut+swePxQ7RWEdAZRp5rJoE1hjsIqiYcKa/ZkOQ+ujPI39Q==} - engines: {node: '>= 14'} + /@octokit/plugin-retry@7.1.1(@octokit/core@6.1.2): + resolution: {integrity: sha512-G9Ue+x2odcb8E1XIPhaFBnTTIrrUDfXN05iFXiqhR+SeeeDMMILcAnysOsxUpEWcQp2e5Ft397FCXTcPkiPkLw==} + engines: {node: '>= 18'} peerDependencies: - '@octokit/core': ^4.0.0 + '@octokit/core': '>=6' dependencies: - '@octokit/core': 4.2.1 - '@octokit/types': 9.3.1 + '@octokit/core': 6.1.2 + '@octokit/request-error': 6.1.4 + '@octokit/types': 13.5.0 bottleneck: 2.19.5 dev: false - /@octokit/request-error@3.0.3: - resolution: {integrity: sha512-crqw3V5Iy2uOU5Np+8M/YexTlT8zxCfI+qu+LxUB7SZpje4Qmx3mub5DfEKSO8Ylyk0aogi6TYdf6kxzh2BguQ==} - engines: {node: '>= 14'} + /@octokit/plugin-throttling@9.3.1(@octokit/core@6.1.2): + resolution: {integrity: sha512-Qd91H4liUBhwLB2h6jZ99bsxoQdhgPk6TdwnClPyTBSDAdviGPceViEgUwj+pcQDmB/rfAXAXK7MTochpHM3yQ==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': ^6.0.0 dependencies: - '@octokit/types': 9.3.1 - deprecation: 2.3.1 - once: 1.4.0 + '@octokit/core': 6.1.2 + '@octokit/types': 13.5.0 + bottleneck: 2.19.5 dev: false - /@octokit/request@6.2.5: - resolution: {integrity: sha512-z83E8UIlPNaJUsXpjD8E0V5o/5f+vJJNbNcBwVZsX3/vC650U41cOkTLjq4PKk9BYonQGOnx7N17gvLyNjgGcQ==} - engines: {node: '>= 14'} + /@octokit/request-error@6.1.4: + resolution: {integrity: sha512-VpAhIUxwhWZQImo/dWAN/NpPqqojR6PSLgLYAituLM6U+ddx9hCioFGwBr5Mi+oi5CLeJkcAs3gJ0PYYzU6wUg==} + engines: {node: '>= 18'} dependencies: - '@octokit/endpoint': 7.0.6 - '@octokit/request-error': 3.0.3 - '@octokit/types': 9.3.1 - is-plain-object: 5.0.0 - node-fetch: 2.6.11 - universal-user-agent: 6.0.0 - transitivePeerDependencies: - - encoding - dev: false - - /@octokit/tsconfig@1.0.2: - resolution: {integrity: sha512-I0vDR0rdtP8p2lGMzvsJzbhdOWy405HcGovrspJ8RRibHnyRgggUSNO5AIox5LmqiwmatHKYsvj6VGFHkqS7lA==} + '@octokit/types': 13.5.0 dev: false - /@octokit/types@9.3.1: - resolution: {integrity: sha512-zfJzyXLHC42sWcn2kS+oZ/DRvFZBYCCbfInZtwp1Uopl1qh6pRg4NSP/wFX1xCOpXvEkctiG1sxlSlkZmzvxdw==} + /@octokit/request@9.1.3: + resolution: {integrity: sha512-V+TFhu5fdF3K58rs1pGUJIDH5RZLbZm5BI+MNF+6o/ssFNT4vWlCh/tVpF3NxGtP15HUxTTMUbsG5llAuU2CZA==} + engines: {node: '>= 18'} dependencies: - '@octokit/openapi-types': 18.0.0 + '@octokit/endpoint': 10.1.1 + '@octokit/request-error': 6.1.4 + '@octokit/types': 13.5.0 + universal-user-agent: 7.0.2 dev: false - /@octokit/webhooks-methods@3.0.3: - resolution: {integrity: sha512-2vM+DCNTJ5vL62O5LagMru6XnYhV4fJslK+5YUkTa6rWlW2S+Tqs1lF9Wr9OGqHfVwpBj3TeztWfVON/eUoW1Q==} - engines: {node: '>= 14'} + /@octokit/types@13.5.0: + resolution: {integrity: sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ==} + dependencies: + '@octokit/openapi-types': 22.2.0 dev: false - /@octokit/webhooks-types@6.11.0: - resolution: {integrity: sha512-AanzbulOHljrku1NGfafxdpTCfw2ENaWzH01N2vqQM+cUFbk868Cgh0xylz0JIM9BoKbfI++bdD6EYX0Q/UTEw==} + /@octokit/webhooks-methods@5.1.0: + resolution: {integrity: sha512-yFZa3UH11VIxYnnoOYCVoJ3q4ChuSOk2IVBBQ0O3xtKX4x9bmKb/1t+Mxixv2iUhzMdOl1qeWJqEhouXXzB3rQ==} + engines: {node: '>= 18'} dev: false - /@octokit/webhooks@10.9.1: - resolution: {integrity: sha512-5NXU4VfsNOo2VSU/SrLrpPH2Z1ZVDOWFcET4EpnEBX1uh/v8Uz65UVuHIRx5TZiXhnWyRE9AO1PXHa+M/iWwZA==} - engines: {node: '>= 14'} + /@octokit/webhooks@13.3.0: + resolution: {integrity: sha512-TUkJLtI163Bz5+JK0O+zDkQpn4gKwN+BovclUvCj6pI/6RXrFqQvUMRS2M+Rt8Rv0qR3wjoMoOPmpJKeOh0nBg==} + engines: {node: '>= 18'} dependencies: - '@octokit/request-error': 3.0.3 - '@octokit/webhooks-methods': 3.0.3 - '@octokit/webhooks-types': 6.11.0 - aggregate-error: 3.1.0 + '@octokit/openapi-webhooks-types': 8.3.0 + '@octokit/request-error': 6.1.4 + '@octokit/webhooks-methods': 5.1.0 dev: false /@open-draft/until@1.0.3: @@ -8486,8 +8465,8 @@ packages: resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} dev: false - /@prisma/client@5.6.0(prisma@5.6.0): - resolution: {integrity: sha512-mUDefQFa1wWqk4+JhKPYq8BdVoFk9NFMBXUI8jAkBfQTtgx8WPx02U2HB/XbAz3GSUJpeJOKJQtNvaAIDs6sug==} + /@prisma/client@5.18.0(prisma@5.18.0): + resolution: {integrity: sha512-BWivkLh+af1kqC89zCJYkHsRcyWsM8/JHpsDMM76DjP3ZdEquJhXa4IeX+HkWPnwJ5FanxEJFZZDTWiDs/Kvyw==} engines: {node: '>=16.13'} requiresBuild: true peerDependencies: @@ -8496,17 +8475,35 @@ packages: prisma: optional: true dependencies: - '@prisma/engines-version': 5.6.0-32.e95e739751f42d8ca026f6b910f5a2dc5adeaeee - prisma: 5.6.0 + prisma: 5.18.0 dev: false - /@prisma/engines-version@5.6.0-32.e95e739751f42d8ca026f6b910f5a2dc5adeaeee: - resolution: {integrity: sha512-UoFgbV1awGL/3wXuUK3GDaX2SolqczeeJ5b4FVec9tzeGbSWJboPSbT0psSrmgYAKiKnkOPFSLlH6+b+IyOwAw==} - dev: false + /@prisma/debug@5.18.0: + resolution: {integrity: sha512-f+ZvpTLidSo3LMJxQPVgAxdAjzv5OpzAo/eF8qZqbwvgi2F5cTOI9XCpdRzJYA0iGfajjwjOKKrVq64vkxEfUw==} - /@prisma/engines@5.6.0: - resolution: {integrity: sha512-Mt2q+GNJpU2vFn6kif24oRSBQv1KOkYaterQsi0k2/lA+dLvhRX6Lm26gon6PYHwUM8/h8KRgXIUMU0PCLB6bw==} + /@prisma/engines-version@5.18.0-25.4c784e32044a8a016d99474bd02a3b6123742169: + resolution: {integrity: sha512-a/+LpJj8vYU3nmtkg+N3X51ddbt35yYrRe8wqHTJtYQt7l1f8kjIBcCs6sHJvodW/EK5XGvboOiwm47fmNrbgg==} + + /@prisma/engines@5.18.0: + resolution: {integrity: sha512-ofmpGLeJ2q2P0wa/XaEgTnX/IsLnvSp/gZts0zjgLNdBhfuj2lowOOPmDcfKljLQUXMvAek3lw5T01kHmCG8rg==} requiresBuild: true + dependencies: + '@prisma/debug': 5.18.0 + '@prisma/engines-version': 5.18.0-25.4c784e32044a8a016d99474bd02a3b6123742169 + '@prisma/fetch-engine': 5.18.0 + '@prisma/get-platform': 5.18.0 + + /@prisma/fetch-engine@5.18.0: + resolution: {integrity: sha512-I/3u0x2n31rGaAuBRx2YK4eB7R/1zCuayo2DGwSpGyrJWsZesrV7QVw7ND0/Suxeo/vLkJ5OwuBqHoCxvTHpOg==} + dependencies: + '@prisma/debug': 5.18.0 + '@prisma/engines-version': 5.18.0-25.4c784e32044a8a016d99474bd02a3b6123742169 + '@prisma/get-platform': 5.18.0 + + /@prisma/get-platform@5.18.0: + resolution: {integrity: sha512-Tk+m7+uhqcKDgnMnFN0lRiH7Ewea0OEsZZs9pqXa7i3+7svS3FSCqDBCaM9x5fmhhkufiG0BtunJVDka+46DlA==} + dependencies: + '@prisma/debug': 5.18.0 /@radix-ui/number@1.0.1: resolution: {integrity: sha512-T5gIdVO2mmPW3NNhjNgEP3cqMXjXL9UbO0BzWcXfvdBs+BohbQxvd/K5hSVKmn9/lbTdsQVKbUcP5WLCwvUbBg==} @@ -11906,10 +11903,6 @@ packages: '@types/node': 20.3.1 dev: true - /@types/btoa-lite@1.0.0: - resolution: {integrity: sha512-wJsiX1tosQ+J5+bY5LrSahHxr2wT+uME5UDwdN1kg4frt40euqA+wzECkmq4t5QbveHiJepfdThgQrPw6KiSlg==} - dev: false - /@types/connect-history-api-fallback@1.5.4: resolution: {integrity: sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==} dependencies: @@ -12100,7 +12093,7 @@ packages: /@types/graceful-fs@4.1.6: resolution: {integrity: sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==} dependencies: - '@types/node': 20.3.1 + '@types/node': 20.14.14 dev: true /@types/hast@2.3.8: @@ -12180,12 +12173,6 @@ packages: /@types/json-schema@7.0.15: resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - /@types/jsonwebtoken@9.0.2: - resolution: {integrity: sha512-drE6uz7QBKq1fYqqoFKTDRdFCPHd5TCub75BM+D+cMx7NU9hUz7SESLfC2fSCXVFMO5Yj8sOWHuGqPgjc+fz0Q==} - dependencies: - '@types/node': 18.16.17 - dev: false - /@types/katex@0.16.7: resolution: {integrity: sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==} dev: false @@ -12282,9 +12269,8 @@ packages: /@types/node-fetch@2.6.4: resolution: {integrity: sha512-1ZX9fcN4Rvkvgv4E6PAY5WXUFWFcRWxZa3EW83UjycOB9ljJCedb2CupIP4RZMEwF/M3eTcCihbBRgwtGbg5Rg==} dependencies: - '@types/node': 18.16.17 + '@types/node': 20.14.14 form-data: 3.0.1 - dev: true /@types/node-forge@1.3.11: resolution: {integrity: sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==} @@ -12307,7 +12293,6 @@ packages: resolution: {integrity: sha512-d64f00982fS9YoOgJkAMolK7MN8Iq3TDdVjchbYHdEmjth/DHowx82GnoA+tVUAN+7vxfYUgAzi+JXbKNd2SDQ==} dependencies: undici-types: 5.26.5 - dev: true /@types/node@20.3.1: resolution: {integrity: sha512-EhcH/wvidPy1WeML3TtYFGR83UzjxeWRen9V402T8aUGYsCHOmfoisV3ZSg03gAFIbLq8TnWOJ0f4cALtnSEUg==} @@ -12940,12 +12925,20 @@ packages: - supports-color dev: true + /agentkeepalive@4.5.0: + resolution: {integrity: sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==} + engines: {node: '>= 8.0.0'} + dependencies: + humanize-ms: 1.2.1 + dev: false + /aggregate-error@3.1.0: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} engines: {node: '>=8'} dependencies: clean-stack: 2.2.0 indent-string: 4.0.0 + dev: true /ajv-formats@2.1.1(ajv@8.12.0): resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} @@ -13229,7 +13222,6 @@ packages: /asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - dev: true /autoprefixer@10.4.14(postcss@8.4.24): resolution: {integrity: sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==} @@ -13428,8 +13420,8 @@ packages: resolution: {integrity: sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==} dev: true - /before-after-hook@2.2.3: - resolution: {integrity: sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==} + /before-after-hook@3.0.2: + resolution: {integrity: sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==} dev: false /better-opn@2.1.1: @@ -13608,18 +13600,10 @@ packages: node-int64: 0.4.0 dev: true - /btoa-lite@1.0.0: - resolution: {integrity: sha512-gvW7InbIyF8AicrqWoptdW08pUxuhq8BEgowNajy9RhiE86fmGAGl+bLKo6oB8QP0CkqHLowfN0oJdKC/J6LbA==} - dev: false - /buffer-crc32@0.2.13: resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} dev: true - /buffer-equal-constant-time@1.0.1: - resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} - dev: false - /buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} @@ -13964,6 +13948,7 @@ packages: /clean-stack@2.2.0: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} + dev: true /clear@0.1.0: resolution: {integrity: sha512-qMjRnoL+JDPJHeLePZJuao6+8orzHMGP04A8CdwCNsKhRbOnKRjefxONR7bwILT3MHecxKBjHkKL/tkZ8r4Uzw==} @@ -14156,7 +14141,6 @@ packages: engines: {node: '>= 0.8'} dependencies: delayed-stream: 1.0.0 - dev: true /comma-separated-tokens@2.0.3: resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} @@ -15111,7 +15095,6 @@ packages: /delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} - dev: true /delegates@1.0.0: resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} @@ -15136,10 +15119,6 @@ packages: engines: {node: '>=4'} dev: true - /deprecation@2.3.1: - resolution: {integrity: sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==} - dev: false - /dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -15361,12 +15340,6 @@ packages: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} dev: true - /ecdsa-sig-formatter@1.0.11: - resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} - dependencies: - safe-buffer: 5.2.1 - dev: false - /editorconfig@0.15.3: resolution: {integrity: sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g==} hasBin: true @@ -16467,6 +16440,10 @@ packages: signal-exit: 4.1.0 dev: true + /form-data-encoder@1.7.2: + resolution: {integrity: sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==} + dev: false + /form-data@3.0.1: resolution: {integrity: sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==} engines: {node: '>= 6'} @@ -16474,7 +16451,6 @@ packages: asynckit: 0.4.0 combined-stream: 1.0.8 mime-types: 2.1.35 - dev: true /form-data@4.0.0: resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} @@ -16485,6 +16461,14 @@ packages: mime-types: 2.1.35 dev: true + /formdata-node@4.4.1: + resolution: {integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==} + engines: {node: '>= 12.20'} + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 4.0.0-beta.3 + dev: false + /formdata-polyfill@4.0.10: resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} engines: {node: '>=12.20.0'} @@ -16536,10 +16520,6 @@ packages: engines: {node: '>= 0.6'} dev: true - /fromentries@1.3.2: - resolution: {integrity: sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==} - dev: false - /fs-constants@1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} dev: true @@ -17466,6 +17446,12 @@ packages: engines: {node: '>=16.17.0'} dev: true + /humanize-ms@1.2.1: + resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + dependencies: + ms: 2.1.3 + dev: false + /hyperdyperid@1.2.0: resolution: {integrity: sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==} engines: {node: '>=10.18'} @@ -17563,6 +17549,7 @@ packages: /indent-string@4.0.0: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} engines: {node: '>=8'} + dev: true /inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} @@ -17959,11 +17946,6 @@ packages: isobject: 3.0.1 dev: true - /is-plain-object@5.0.0: - resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} - engines: {node: '>=0.10.0'} - dev: false - /is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} dev: true @@ -18357,7 +18339,7 @@ packages: resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} engines: {node: '>= 10.13.0'} dependencies: - '@types/node': 20.3.1 + '@types/node': 18.16.17 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -18365,7 +18347,7 @@ packages: resolution: {integrity: sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@types/node': 20.3.1 + '@types/node': 20.14.14 jest-util: 29.5.0 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -18598,31 +18580,6 @@ packages: engines: {'0': node >= 0.2.0} dev: true - /jsonwebtoken@9.0.0: - resolution: {integrity: sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==} - engines: {node: '>=12', npm: '>=6'} - dependencies: - jws: 3.2.2 - lodash: 4.17.21 - ms: 2.1.3 - semver: 7.6.0 - dev: false - - /jwa@1.4.1: - resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==} - dependencies: - buffer-equal-constant-time: 1.0.1 - ecdsa-sig-formatter: 1.0.11 - safe-buffer: 5.2.1 - dev: false - - /jws@3.2.2: - resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} - dependencies: - jwa: 1.4.1 - safe-buffer: 5.2.1 - dev: false - /karma-chrome-launcher@3.1.0: resolution: {integrity: sha512-3dPs/n7vgz1rxxtynpzZTvb9y/GIaW8xjAwcIGttLbycqoFtI7yo1NGnQi6oFTherRE+GIhCAHZC4vEqWGhNvg==} dependencies: @@ -19119,7 +19076,6 @@ packages: /lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - dev: true /lru-cache@4.1.5: resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==} @@ -19141,6 +19097,7 @@ packages: /lru-cache@9.1.2: resolution: {integrity: sha512-ERJq3FOzJTxBbFjZ7iDs+NiK4VI9Wz+RdrrAB8dio1oV+YvdPzUEE4QNiT2VD51DkIbCYRUUzCRkssXCHqSnKQ==} engines: {node: 14 || >=16.14} + dev: true /lucide-react@0.320.0(react@18.2.0): resolution: {integrity: sha512-HuMmfmFiWwctDkN27wklKVZr8UpwP2TTekLZ3xiLCEjx/STG1k0KLWMbBfIJ/lnNJQDfSjztDZSVU316xA+AQg==} @@ -19647,6 +19604,13 @@ packages: tslib: 2.6.3 dev: true + /memoize@10.0.0: + resolution: {integrity: sha512-H6cBLgsi6vMWOcCpvVCdFFnl3kerEXbrYh9q+lY6VXvQSmM6CkmV08VOwT+WE2tzIEqRPFfAq3fm4v/UIW6mSA==} + engines: {node: '>=18'} + dependencies: + mimic-function: 5.0.1 + dev: false + /memoizerific@1.11.3: resolution: {integrity: sha512-/EuHYwAPdLtXwAwSZkh/Gutery6pD2KYd44oQLhAvQp/50mpyduZh8Q7PYHXTCJ+wuXxt7oij2LXyIJOOYFPog==} dependencies: @@ -20378,7 +20342,6 @@ packages: /mimic-function@5.0.1: resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} engines: {node: '>=18'} - dev: true /min-indent@1.0.1: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} @@ -21479,20 +21442,20 @@ packages: resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==} dev: true - /octokit@2.0.19: - resolution: {integrity: sha512-hSloK4MK78QGbAuBrtIir0bsxMoRVZE5CkwKSbSRH9lqv2hx9EwhCxtPqEF+BtHqLXkXdfUaGkJMyMBotYno+A==} - engines: {node: '>= 14'} + /octokit@4.0.2: + resolution: {integrity: sha512-wbqF4uc1YbcldtiBFfkSnquHtECEIpYD78YUXI6ri1Im5OO2NLo6ZVpRdbJpdnpZ05zMrVPssNiEo6JQtea+Qg==} + engines: {node: '>= 18'} dependencies: - '@octokit/app': 13.1.5 - '@octokit/core': 4.2.1 - '@octokit/oauth-app': 4.2.2 - '@octokit/plugin-paginate-rest': 6.1.2(@octokit/core@4.2.1) - '@octokit/plugin-rest-endpoint-methods': 7.2.1(@octokit/core@4.2.1) - '@octokit/plugin-retry': 4.1.6(@octokit/core@4.2.1) - '@octokit/plugin-throttling': 5.2.3(@octokit/core@4.2.1) - '@octokit/types': 9.3.1 - transitivePeerDependencies: - - encoding + '@octokit/app': 15.1.0 + '@octokit/core': 6.1.2 + '@octokit/oauth-app': 7.1.3 + '@octokit/plugin-paginate-graphql': 5.2.2(@octokit/core@6.1.2) + '@octokit/plugin-paginate-rest': 11.3.3(@octokit/core@6.1.2) + '@octokit/plugin-rest-endpoint-methods': 13.2.4(@octokit/core@6.1.2) + '@octokit/plugin-retry': 7.1.1(@octokit/core@6.1.2) + '@octokit/plugin-throttling': 9.3.1(@octokit/core@6.1.2) + '@octokit/request-error': 6.1.4 + '@octokit/types': 13.5.0 dev: false /oidc-token-hash@5.0.3: @@ -21575,6 +21538,27 @@ packages: is-wsl: 2.2.0 dev: true + /openai@4.56.0(zod@3.21.4): + resolution: {integrity: sha512-zcag97+3bG890MNNa0DQD9dGmmTWL8unJdNkulZzWRXrl+QeD+YkBI4H58rJcwErxqGK6a0jVPZ4ReJjhDGcmw==} + hasBin: true + peerDependencies: + zod: ^3.23.8 + peerDependenciesMeta: + zod: + optional: true + dependencies: + '@types/node': 18.16.17 + '@types/node-fetch': 2.6.4 + abort-controller: 3.0.0 + agentkeepalive: 4.5.0 + form-data-encoder: 1.7.2 + formdata-node: 4.4.1 + node-fetch: 2.6.11 + zod: 3.21.4 + transitivePeerDependencies: + - encoding + dev: false + /opener@1.5.2: resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==} hasBin: true @@ -22319,7 +22303,6 @@ packages: resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} engines: {node: '>=10.13.0'} hasBin: true - dev: true /prettier@3.0.0: resolution: {integrity: sha512-zBf5eHpwHOGPC47h0zrPyNn+eAEIdEzfywMoYn2XPi0P44Zp0tSq64rq0xAREh4auw2cJZHo9QUob+NqCQky4g==} @@ -22382,13 +22365,13 @@ packages: resolution: {integrity: sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==} dev: true - /prisma@5.6.0: - resolution: {integrity: sha512-EEaccku4ZGshdr2cthYHhf7iyvCcXqwJDvnoQRAJg5ge2Tzpv0e2BaMCp+CbbDUwoVTzwgOap9Zp+d4jFa2O9A==} + /prisma@5.18.0: + resolution: {integrity: sha512-+TrSIxZsh64OPOmaSgVPH7ALL9dfU0jceYaMJXsNrTkFHO7/3RANi5K2ZiPB1De9+KDxCWn7jvRq8y8pvk+o9g==} engines: {node: '>=16.13'} hasBin: true requiresBuild: true dependencies: - '@prisma/engines': 5.6.0 + '@prisma/engines': 5.18.0 /proc-log@4.2.0: resolution: {integrity: sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==} @@ -25261,6 +25244,7 @@ packages: /tslib@2.6.3: resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==} + requiresBuild: true /tsscmp@1.0.6: resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==} @@ -25525,7 +25509,6 @@ packages: /undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - dev: true /undici-types@6.13.0: resolution: {integrity: sha512-xtFJHudx8S2DSoujjMd1WeWvn7KKWFRESZTMeL1RptAYERu29D6jphMjjY+vn96jvN3kVPDNxU/E13VTaXj6jg==} @@ -25768,15 +25751,12 @@ packages: unist-util-visit-parents: 6.0.1 dev: false - /universal-github-app-jwt@1.1.1: - resolution: {integrity: sha512-G33RTLrIBMFmlDV4u4CBF7dh71eWwykck4XgaxaIVeZKOYZRAAxvcGMRFTUclVY6xoUPQvO4Ne5wKGxYm/Yy9w==} - dependencies: - '@types/jsonwebtoken': 9.0.2 - jsonwebtoken: 9.0.0 + /universal-github-app-jwt@2.2.0: + resolution: {integrity: sha512-G5o6f95b5BggDGuUfKDApKaCgNYy2x7OdHY0zSMF081O0EJobw+1130VONhrA7ezGSV2FNOGyM+KQpQZAr9bIQ==} dev: false - /universal-user-agent@6.0.0: - resolution: {integrity: sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==} + /universal-user-agent@7.0.2: + resolution: {integrity: sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==} dev: false /universalify@0.1.2: @@ -26542,6 +26522,11 @@ packages: resolution: {integrity: sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==} engines: {node: '>= 8'} + /web-streams-polyfill@4.0.0-beta.3: + resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==} + engines: {node: '>= 14'} + dev: false + /web-worker@1.2.0: resolution: {integrity: sha512-PgF341avzqyx60neE9DD+XS26MMNMoUQRz9NOZwW32nPQrF6p77f1htcnjBSEV8BGMKZ16choqUG4hyI0Hx7mA==} dev: false