From 7282255002952805ecf361165a5c835769b66ca9 Mon Sep 17 00:00:00 2001 From: Tim Raderschad Date: Sun, 25 Aug 2024 21:07:28 +0200 Subject: [PATCH] feat(web): add auto flag removal --- 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["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 = ({ Edit Description + { + 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"); + }} + > + + Create Removal PR + { 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(); + 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 ; + if (integrationsQuery.error) return
Error
; + if (integrationsQuery.data.length === 0) { + return ( +
+

No integrations

+
+ ); + } + + return integrationsQuery.data.map((i) => + match(i.type) + .with("GITHUB", () => { + const selectedRepository = i.potentialRepositories.find( + (r) => r.id.toString() === selectedRepositoryId + ); + + return ( + + + Github + + The selected repository will be used to automagically create and + update pull requests for your feature flags. + + + + {i.installedRepos.length === 0 ? ( +
+
+ + + + + + + + + + No framework found. + + {i.potentialRepositories.map((repo) => ( + { + setSelectedRepositoryId( + repo.id.toString() === + selectedRepositoryId + ? undefined + : repo.id.toString() + ); + setOpen(false); + }} + > + + + + {repo.owner}/ + + {repo.name} + + + ))} + + + + + +
+ +
+ ) : ( +
+ Your selected repository is + + + + {i.installedRepos[0]?.owner}/ + + {i.installedRepos[0]?.name} + + + Need to change this? Contact us +
+ )} +
+
+ ); + }) + .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< 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, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)); +SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName; + +const SelectScrollDownButton = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)); +SelectScrollDownButton.displayName = + SelectPrimitive.ScrollDownButton.displayName; + const SelectContent = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef @@ -38,7 +73,7 @@ const SelectContent = React.forwardRef< + {children} + )); @@ -79,12 +116,12 @@ const SelectItem = React.forwardRef< - + @@ -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(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 = () => { + + + Integrations + + Manage your integrations + +
+ {data.project.integrations.length === 0 ? ( +
+ + + +
+ ) : ( + + )} +
+
+
Usage 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 & { }; 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