diff --git a/apps/webservice/package.json b/apps/webservice/package.json index 6f594db06..4613c2e91 100644 --- a/apps/webservice/package.json +++ b/apps/webservice/package.json @@ -24,6 +24,8 @@ "@internationalized/date": "^3.5.4", "@monaco-editor/react": "^4.6.0", "@octokit/auth-app": "^7.1.0", + "@octokit/rest": "^20.1.1", + "@octokit/webhooks-types": "^7.5.1", "@opentelemetry/api-logs": "^0.52.1", "@opentelemetry/instrumentation": "^0.52.1", "@opentelemetry/sdk-logs": "^0.52.1", diff --git a/apps/webservice/src/app/[workspaceSlug]/settings/(settings)/workspace/integrations/(integration)/github/GithubConfigFile.tsx b/apps/webservice/src/app/[workspaceSlug]/settings/(settings)/workspace/integrations/(integration)/github/GithubConfigFile.tsx index 3cd076bcb..040871f7e 100644 --- a/apps/webservice/src/app/[workspaceSlug]/settings/(settings)/workspace/integrations/(integration)/github/GithubConfigFile.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/settings/(settings)/workspace/integrations/(integration)/github/GithubConfigFile.tsx @@ -1,3 +1,4 @@ +import type { GithubConfigFile } from "@ctrlplane/db/schema"; import { Card, CardContent, @@ -7,15 +8,9 @@ import { } from "@ctrlplane/ui/card"; import { Separator } from "@ctrlplane/ui/separator"; -import { api } from "~/trpc/react"; - export const GithubConfigFileSync: React.FC<{ - workspaceId?: string; -}> = ({ workspaceId }) => { - const configFiles = api.github.configFile.list.useQuery(workspaceId ?? "", { - enabled: workspaceId != null, - }); - + configFiles: GithubConfigFile[]; +}> = ({ configFiles }) => { return ( @@ -31,8 +26,8 @@ export const GithubConfigFileSync: React.FC<{ - {configFiles.data?.map((configFile) => ( -
{configFile.name}
+ {configFiles.map((configFile) => ( +
{configFile.path}
))}
diff --git a/apps/webservice/src/app/[workspaceSlug]/settings/(settings)/workspace/integrations/(integration)/github/page.tsx b/apps/webservice/src/app/[workspaceSlug]/settings/(settings)/workspace/integrations/(integration)/github/page.tsx index 78037835a..3b21573a4 100644 --- a/apps/webservice/src/app/[workspaceSlug]/settings/(settings)/workspace/integrations/(integration)/github/page.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/settings/(settings)/workspace/integrations/(integration)/github/page.tsx @@ -29,6 +29,11 @@ export default function GitHubIntegrationPage({ enabled: session.status === "authenticated", }); + const configFiles = api.github.configFile.list.useQuery( + workspace.data?.id ?? "", + { enabled: workspace.data != null }, + ); + return (
@@ -77,7 +82,7 @@ export default function GitHubIntegrationPage({ loading={workspace.isLoading || githubUser.isLoading} /> - +
); } diff --git a/apps/webservice/src/app/api/github/config/handle-configs.ts b/apps/webservice/src/app/api/github/config/handle-configs.ts new file mode 100644 index 000000000..7ae27091a --- /dev/null +++ b/apps/webservice/src/app/api/github/config/handle-configs.ts @@ -0,0 +1,207 @@ +import { and, eq, inArray } from "@ctrlplane/db"; +import { db } from "@ctrlplane/db/client"; +import type { + GithubOrganization} from "@ctrlplane/db/schema"; +import { + deployment, + githubConfigFile, + system, + workspace, +} from "@ctrlplane/db/schema"; + +interface DeploymentConfig { + name: string; + slug: string; + system: string; + workspace: string; + description?: string; +} + +interface ParsedConfig { + path: string; + repositoryName: string; + branch: string; + deployments: DeploymentConfig[]; +} + +export const handleNewConfigs = async ( + newParsedConfigs: ParsedConfig[], + internalOrganization: GithubOrganization, + repositoryName: string, +) => { + if (newParsedConfigs.length === 0) return; + + const newConfigs = await db + .insert(githubConfigFile) + .values( + newParsedConfigs.map((cf) => ({ + ...cf, + workspaceId: internalOrganization.workspaceId, + organizationId: internalOrganization.id, + repositoryName: repositoryName, + })), + ) + .returning(); + + const newDeploymentInfo = await db + .select() + .from(system) + .innerJoin(workspace, eq(system.workspaceId, workspace.id)) + .where( + and( + inArray( + system.slug, + newParsedConfigs + .map((d) => d.deployments.map((d) => d.system)) + .flat(), + ), + inArray( + workspace.slug, + newParsedConfigs + .map((d) => d.deployments.map((d) => d.workspace)) + .flat(), + ), + ), + ); + + const deploymentsFromNewConfigs = newParsedConfigs + .map((cf) => + cf.deployments.map((d) => { + const info = newDeploymentInfo.find( + (i) => i.system.slug === d.system && i.workspace.slug === d.workspace, + ); + if (info == null) throw new Error("Deployment info not found"); + const { system, workspace } = info; + + return { + ...d, + systemId: system.id, + workspaceId: workspace.id, + description: d.description ?? "", + githubConfigFileId: newConfigs.find( + (icf) => + icf.path === cf.path && icf.repositoryName === cf.repositoryName, + )?.id, + }; + }), + ) + .flat(); + + await db.insert(deployment).values(deploymentsFromNewConfigs); +}; + +export const handleModifiedConfigs = async ( + modifiedParsedConfigs: ParsedConfig[], + internalOrganization: GithubOrganization, + repositoryName: string, +) => { + if (modifiedParsedConfigs.length === 0) return; + const existingConfigs = await db + .select() + .from(githubConfigFile) + .where( + and( + inArray( + githubConfigFile.path, + modifiedParsedConfigs.map((cf) => cf.path), + ), + eq(githubConfigFile.organizationId, internalOrganization.id), + eq(githubConfigFile.repositoryName, repositoryName), + ), + ); + + const existingDeployments = await db + .select() + .from(deployment) + .innerJoin(system, eq(deployment.systemId, system.id)) + .innerJoin(workspace, eq(system.workspaceId, workspace.id)) + .where( + inArray( + deployment.githubConfigFileId, + existingConfigs.map((c) => c.id), + ), + ); + + const deploymentsInConfigFiles = modifiedParsedConfigs + .map((d) => d.deployments) + .flat(); + + const removedDeployments = existingDeployments.filter( + (ed) => + !deploymentsInConfigFiles.some( + (dc) => + dc.slug === ed.deployment.slug && + dc.system === ed.system.slug && + dc.workspace === ed.workspace.slug, + ), + ); + + if (removedDeployments.length > 0) + await db.delete(deployment).where( + inArray( + deployment.id, + removedDeployments.map((d) => d.deployment.id), + ), + ); + + const newDeployments = modifiedParsedConfigs + .map((cf) => + cf.deployments.map((d) => ({ + ...d, + cf: { + path: cf.path, + repositoryName: cf.repositoryName, + }, + })), + ) + .flat() + .filter( + (d) => + !existingDeployments.some( + (ed) => + ed.deployment.slug === d.slug && + ed.system.slug === d.system && + ed.workspace.slug === d.workspace, + ), + ); + + if (newDeployments.length === 0) return; + + const newDeploymentInfo = await db + .select() + .from(system) + .innerJoin(workspace, eq(system.workspaceId, workspace.id)) + .where( + and( + inArray( + system.slug, + newDeployments.map((d) => d.system), + ), + inArray( + workspace.slug, + newDeployments.map((d) => d.workspace), + ), + ), + ); + + const newDeploymentsInsert = newDeployments.map((d) => { + const info = newDeploymentInfo.find( + (i) => i.system.slug === d.system && i.workspace.slug === d.workspace, + ); + if (info == null) throw new Error("Deployment info not found"); + const { system, workspace } = info; + + return { + ...d, + systemId: system.id, + workspaceId: workspace.id, + description: d.description ?? "", + githubConfigFileId: existingConfigs.find( + (icf) => + icf.path === d.cf.path && icf.repositoryName === d.cf.repositoryName, + )?.id, + }; + }); + + await db.insert(deployment).values(newDeploymentsInsert); +}; diff --git a/apps/webservice/src/app/api/github/config/route.ts b/apps/webservice/src/app/api/github/config/route.ts new file mode 100644 index 000000000..6629a3753 --- /dev/null +++ b/apps/webservice/src/app/api/github/config/route.ts @@ -0,0 +1,174 @@ +import type { NextRequest } from "next/server"; +import { NextResponse } from "next/server"; +import { createAppAuth } from "@octokit/auth-app"; +import { Octokit } from "@octokit/rest"; +import type { PushEvent } from "@octokit/webhooks-types"; +import * as yaml from "js-yaml"; +import { isPresent } from "ts-is-present"; + +import { and, eq, inArray, takeFirstOrNull } from "@ctrlplane/db"; +import { db } from "@ctrlplane/db/client"; +import { githubConfigFile, githubOrganization } from "@ctrlplane/db/schema"; +import { configFile } from "@ctrlplane/validators"; + +import { env } from "~/env"; +import { handleModifiedConfigs, handleNewConfigs } from "./handle-configs"; + +const getOctokitInstallation = (installationId: number) => + env.GITHUB_BOT_APP_ID == null || + env.GITHUB_BOT_PRIVATE_KEY == null || + env.GITHUB_BOT_CLIENT_ID == null || + env.GITHUB_BOT_CLIENT_SECRET == null + ? null + : new Octokit({ + authStrategy: createAppAuth, + auth: { + appId: env.GITHUB_BOT_APP_ID, + privateKey: env.GITHUB_BOT_PRIVATE_KEY, + clientId: env.GITHUB_BOT_CLIENT_ID, + clientSecret: env.GITHUB_BOT_CLIENT_SECRET, + installationId, + }, + }); + +const isConfigFile = (path: string) => + path.endsWith("ctrlplane.yaml") || path.endsWith("ctrlplane.yml"); + +interface GetParsedConfigOptions { + organization: string; + repository: string; + path: string; + branch: string; +} + +const getParsedConfig = ( + { organization, repository, path, branch }: GetParsedConfigOptions, + installationOctokit: Octokit, +) => + installationOctokit.repos + .getContent({ + owner: organization, + repo: repository, + path, + ref: branch, + }) + .then(({ data }) => { + if (!("content" in data)) throw new Error("Invalid response data"); + const content = Buffer.from(data.content, "base64").toString("utf-8"); + const yamlContent = yaml.load(content); + const parsed = configFile.safeParse(yamlContent); + if (!parsed.success) throw new Error("Invalid config file"); + return { + ...parsed.data, + repositoryName: repository, + path, + branch, + }; + }); + +const handleCommitWebhookEvent = async (event: PushEvent) => { + const { ref, organization, repository, commits: eventCommits } = event; + if (organization == null) + throw new Error("Event not associated with an organization"); + + const branch = ref.split("/").pop(); + + const internalOrganization = await db + .select() + .from(githubOrganization) + .where(eq(githubOrganization.organizationName, organization.login)) + .then(takeFirstOrNull); + + if (internalOrganization == null) throw new Error("Organization not found"); + + if (internalOrganization.branch !== branch) return; + + const removedConfigFiles = eventCommits + .flatMap((v) => v.removed) + .filter(isConfigFile); + + if (removedConfigFiles.length > 0) + await db + .delete(githubConfigFile) + .where( + and( + inArray(githubConfigFile.path, removedConfigFiles), + eq(githubConfigFile.organizationId, internalOrganization.id), + ), + ); + + const newConfigFiles = eventCommits + .flatMap((v) => v.added) + .filter(isConfigFile); + const modifiedConfigFiles = eventCommits + .flatMap((v) => v.modified) + .filter(isConfigFile); + + const installationOctokit = getOctokitInstallation( + internalOrganization.installationId, + ); + + if (installationOctokit == null) throw new Error("GitHub bot not configured"); + + const [newParsedConfigs, modifiedParsedConfigs] = await Promise.all([ + Promise.allSettled( + newConfigFiles.map(async (cf) => { + return getParsedConfig( + { + organization: internalOrganization.organizationName, + repository: repository.name, + path: cf, + branch: internalOrganization.branch, + }, + installationOctokit, + ); + }), + ).then((results) => + results + .map((r) => (r.status === "fulfilled" ? r.value : null)) + .filter(isPresent), + ), + + Promise.allSettled( + modifiedConfigFiles.map(async (cf) => { + return getParsedConfig( + { + organization: internalOrganization.organizationName, + repository: repository.name, + path: cf, + branch: internalOrganization.branch, + }, + installationOctokit, + ); + }), + ).then((results) => + results + .map((r) => (r.status === "fulfilled" ? r.value : null)) + .filter(isPresent), + ), + ]); + + await handleModifiedConfigs( + modifiedParsedConfigs, + internalOrganization, + repository.name, + ); + + await handleNewConfigs( + newParsedConfigs, + internalOrganization, + repository.name, + ); +}; + +export const POST = async (req: NextRequest) => { + try { + const event = req.headers.get("x-github-event")?.toString(); + const data = await req.json(); + + if (event === "push") await handleCommitWebhookEvent(data as PushEvent); + return new NextResponse("OK"); + } catch (e) { + return new NextResponse((e as any).message, { status: 500 }); + } +}; diff --git a/apps/webservice/src/env.ts b/apps/webservice/src/env.ts index bc9dec43f..de6e8f7e8 100644 --- a/apps/webservice/src/env.ts +++ b/apps/webservice/src/env.ts @@ -22,6 +22,8 @@ export const env = createEnv({ POSTGRES_URL: z.string().url(), GITHUB_BOT_CLIENT_ID: z.string().optional(), GITHUB_BOT_CLIENT_SECRET: z.string().optional(), + GITHUB_BOT_APP_ID: z.string().optional(), + GITHUB_BOT_PRIVATE_KEY: z.string().optional(), }, /** diff --git a/packages/api/src/router/github.ts b/packages/api/src/router/github.ts index 4761408d0..fc02231d2 100644 --- a/packages/api/src/router/github.ts +++ b/packages/api/src/router/github.ts @@ -256,16 +256,9 @@ export const githubRouter = createTRPCRouter({ installation_id: org.installationId, }) .then(async ({ data: installation }) => { - const installationOctokit = new Octokit({ - authStrategy: createAppAuth, - auth: { - appId: env.GITHUB_BOT_APP_ID, - privateKey: env.GITHUB_BOT_PRIVATE_KEY, - clientId: env.GITHUB_BOT_CLIENT_ID, - clientSecret: env.GITHUB_BOT_CLIENT_SECRET, - installationId: installation.id, - }, - }); + const installationOctokit = getOctokitInstallation( + installation.id, + ); const installationToken = (await installationOctokit.auth({ type: "installation", @@ -367,7 +360,6 @@ export const githubRouter = createTRPCRouter({ workspaceId: org.workspaceId, organizationId: org.id, repositoryName: d.repository.name, - branch: org.branch, })), ) .returning(); @@ -394,7 +386,6 @@ export const githubRouter = createTRPCRouter({ description: d.description ?? "", githubConfigFileId: insertedConfigFiles.find( (icf) => - icf.name === cf.name && icf.path === cf.path && icf.repositoryName === cf.repository.name, )?.id, diff --git a/packages/db/src/schema/github.ts b/packages/db/src/schema/github.ts index 68c0f009e..f3ab02fff 100644 --- a/packages/db/src/schema/github.ts +++ b/packages/db/src/schema/github.ts @@ -5,6 +5,7 @@ import { pgTable, text, timestamp, + uniqueIndex, uuid, } from "drizzle-orm/pg-core"; import { createInsertSchema } from "drizzle-zod"; @@ -45,19 +46,29 @@ export type GithubOrganization = InferSelectModel; export const githubOrganizationInsert = createInsertSchema(githubOrganization); -export const githubConfigFile = pgTable("github_config_file", { - id: uuid("id").primaryKey().defaultRandom(), - organizationId: uuid("organization_id") - .notNull() - .references(() => githubOrganization.id, { onDelete: "cascade" }), - repositoryName: text("repository_name").notNull(), - branch: text("branch").notNull().default("main"), - path: text("path").notNull(), - name: text("name").notNull(), - workspaceId: uuid("workspace_id") - .notNull() - .references(() => workspace.id, { onDelete: "cascade" }), - lastSyncedAt: timestamp("last_synced_at", { - withTimezone: true, - }).defaultNow(), -}); +export const githubConfigFile = pgTable( + "github_config_file", + { + id: uuid("id").primaryKey().defaultRandom(), + organizationId: uuid("organization_id") + .notNull() + .references(() => githubOrganization.id, { onDelete: "cascade" }), + repositoryName: text("repository_name").notNull(), + path: text("path").notNull(), + workspaceId: uuid("workspace_id") + .notNull() + .references(() => workspace.id, { onDelete: "cascade" }), + lastSyncedAt: timestamp("last_synced_at", { + withTimezone: true, + }).defaultNow(), + }, + (t) => ({ + unique: uniqueIndex("unique_organization_repository_path").on( + t.organizationId, + t.repositoryName, + t.path, + ), + }), +); + +export type GithubConfigFile = InferSelectModel; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7a98e23cf..5b1864469 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -322,6 +322,12 @@ importers: '@octokit/auth-app': specifier: ^7.1.0 version: 7.1.0 + '@octokit/rest': + specifier: ^20.1.1 + version: 20.1.1 + '@octokit/webhooks-types': + specifier: ^7.5.1 + version: 7.5.1 '@opentelemetry/api-logs': specifier: ^0.52.1 version: 0.52.1 @@ -2485,6 +2491,9 @@ packages: '@octokit/types@13.5.0': resolution: {integrity: sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ==} + '@octokit/webhooks-types@7.5.1': + resolution: {integrity: sha512-1dozxWEP8lKGbtEu7HkRbK1F/nIPuJXNfT0gd96y6d3LcHZTtRtlf8xz3nicSJfesADxJyDh+mWBOsdLkqgzYw==} + '@openapitools/openapi-generator-cli@2.13.5': resolution: {integrity: sha512-9VgeKOTiiatKSwZDKKB3C86cW8tN9eDcFohotD4eisdK38UQswk/4Ysoq9KChRCbymjoMp6AIDHPtK1DQ2fTgw==} engines: {node: '>=10.0.0'} @@ -3799,83 +3808,83 @@ packages: peerDependencies: '@redis/client': ^1.0.0 - '@rollup/rollup-android-arm-eabi@4.21.0': - resolution: {integrity: sha512-WTWD8PfoSAJ+qL87lE7votj3syLavxunWhzCnx3XFxFiI/BA/r3X7MUM8dVrH8rb2r4AiO8jJsr3ZjdaftmnfA==} + '@rollup/rollup-android-arm-eabi@4.21.1': + resolution: {integrity: sha512-2thheikVEuU7ZxFXubPDOtspKn1x0yqaYQwvALVtEcvFhMifPADBrgRPyHV0TF3b+9BgvgjgagVyvA/UqPZHmg==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.21.0': - resolution: {integrity: sha512-a1sR2zSK1B4eYkiZu17ZUZhmUQcKjk2/j9Me2IDjk1GHW7LB5Z35LEzj9iJch6gtUfsnvZs1ZNyDW2oZSThrkA==} + '@rollup/rollup-android-arm64@4.21.1': + resolution: {integrity: sha512-t1lLYn4V9WgnIFHXy1d2Di/7gyzBWS8G5pQSXdZqfrdCGTwi1VasRMSS81DTYb+avDs/Zz4A6dzERki5oRYz1g==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.21.0': - resolution: {integrity: sha512-zOnKWLgDld/svhKO5PD9ozmL6roy5OQ5T4ThvdYZLpiOhEGY+dp2NwUmxK0Ld91LrbjrvtNAE0ERBwjqhZTRAA==} + '@rollup/rollup-darwin-arm64@4.21.1': + resolution: {integrity: sha512-AH/wNWSEEHvs6t4iJ3RANxW5ZCK3fUnmf0gyMxWCesY1AlUj8jY7GC+rQE4wd3gwmZ9XDOpL0kcFnCjtN7FXlA==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.21.0': - resolution: {integrity: sha512-7doS8br0xAkg48SKE2QNtMSFPFUlRdw9+votl27MvT46vo44ATBmdZdGysOevNELmZlfd+NEa0UYOA8f01WSrg==} + '@rollup/rollup-darwin-x64@4.21.1': + resolution: {integrity: sha512-dO0BIz/+5ZdkLZrVgQrDdW7m2RkrLwYTh2YMFG9IpBtlC1x1NPNSXkfczhZieOlOLEqgXOFH3wYHB7PmBtf+Bg==} cpu: [x64] os: [darwin] - '@rollup/rollup-linux-arm-gnueabihf@4.21.0': - resolution: {integrity: sha512-pWJsfQjNWNGsoCq53KjMtwdJDmh/6NubwQcz52aEwLEuvx08bzcy6tOUuawAOncPnxz/3siRtd8hiQ32G1y8VA==} + '@rollup/rollup-linux-arm-gnueabihf@4.21.1': + resolution: {integrity: sha512-sWWgdQ1fq+XKrlda8PsMCfut8caFwZBmhYeoehJ05FdI0YZXk6ZyUjWLrIgbR/VgiGycrFKMMgp7eJ69HOF2pQ==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.21.0': - resolution: {integrity: sha512-efRIANsz3UHZrnZXuEvxS9LoCOWMGD1rweciD6uJQIx2myN3a8Im1FafZBzh7zk1RJ6oKcR16dU3UPldaKd83w==} + '@rollup/rollup-linux-arm-musleabihf@4.21.1': + resolution: {integrity: sha512-9OIiSuj5EsYQlmwhmFRA0LRO0dRRjdCVZA3hnmZe1rEwRk11Jy3ECGGq3a7RrVEZ0/pCsYWx8jG3IvcrJ6RCew==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.21.0': - resolution: {integrity: sha512-ZrPhydkTVhyeGTW94WJ8pnl1uroqVHM3j3hjdquwAcWnmivjAwOYjTEAuEDeJvGX7xv3Z9GAvrBkEzCgHq9U1w==} + '@rollup/rollup-linux-arm64-gnu@4.21.1': + resolution: {integrity: sha512-0kuAkRK4MeIUbzQYu63NrJmfoUVicajoRAL1bpwdYIYRcs57iyIV9NLcuyDyDXE2GiZCL4uhKSYAnyWpjZkWow==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.21.0': - resolution: {integrity: sha512-cfaupqd+UEFeURmqNP2eEvXqgbSox/LHOyN9/d2pSdV8xTrjdg3NgOFJCtc1vQ/jEke1qD0IejbBfxleBPHnPw==} + '@rollup/rollup-linux-arm64-musl@4.21.1': + resolution: {integrity: sha512-/6dYC9fZtfEY0vozpc5bx1RP4VrtEOhNQGb0HwvYNwXD1BBbwQ5cKIbUVVU7G2d5WRE90NfB922elN8ASXAJEA==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.21.0': - resolution: {integrity: sha512-ZKPan1/RvAhrUylwBXC9t7B2hXdpb/ufeu22pG2psV7RN8roOfGurEghw1ySmX/CmDDHNTDDjY3lo9hRlgtaHg==} + '@rollup/rollup-linux-powerpc64le-gnu@4.21.1': + resolution: {integrity: sha512-ltUWy+sHeAh3YZ91NUsV4Xg3uBXAlscQe8ZOXRCVAKLsivGuJsrkawYPUEyCV3DYa9urgJugMLn8Z3Z/6CeyRQ==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.21.0': - resolution: {integrity: sha512-H1eRaCwd5E8eS8leiS+o/NqMdljkcb1d6r2h4fKSsCXQilLKArq6WS7XBLDu80Yz+nMqHVFDquwcVrQmGr28rg==} + '@rollup/rollup-linux-riscv64-gnu@4.21.1': + resolution: {integrity: sha512-BggMndzI7Tlv4/abrgLwa/dxNEMn2gC61DCLrTzw8LkpSKel4o+O+gtjbnkevZ18SKkeN3ihRGPuBxjaetWzWg==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.21.0': - resolution: {integrity: sha512-zJ4hA+3b5tu8u7L58CCSI0A9N1vkfwPhWd/puGXwtZlsB5bTkwDNW/+JCU84+3QYmKpLi+XvHdmrlwUwDA6kqw==} + '@rollup/rollup-linux-s390x-gnu@4.21.1': + resolution: {integrity: sha512-z/9rtlGd/OMv+gb1mNSjElasMf9yXusAxnRDrBaYB+eS1shFm6/4/xDH1SAISO5729fFKUkJ88TkGPRUh8WSAA==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.21.0': - resolution: {integrity: sha512-e2hrvElFIh6kW/UNBQK/kzqMNY5mO+67YtEh9OA65RM5IJXYTWiXjX6fjIiPaqOkBthYF1EqgiZ6OXKcQsM0hg==} + '@rollup/rollup-linux-x64-gnu@4.21.1': + resolution: {integrity: sha512-kXQVcWqDcDKw0S2E0TmhlTLlUgAmMVqPrJZR+KpH/1ZaZhLSl23GZpQVmawBQGVhyP5WXIsIQ/zqbDBBYmxm5w==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.21.0': - resolution: {integrity: sha512-1vvmgDdUSebVGXWX2lIcgRebqfQSff0hMEkLJyakQ9JQUbLDkEaMsPTLOmyccyC6IJ/l3FZuJbmrBw/u0A0uCQ==} + '@rollup/rollup-linux-x64-musl@4.21.1': + resolution: {integrity: sha512-CbFv/WMQsSdl+bpX6rVbzR4kAjSSBuDgCqb1l4J68UYsQNalz5wOqLGYj4ZI0thGpyX5kc+LLZ9CL+kpqDovZA==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.21.0': - resolution: {integrity: sha512-s5oFkZ/hFcrlAyBTONFY1TWndfyre1wOMwU+6KCpm/iatybvrRgmZVM+vCFwxmC5ZhdlgfE0N4XorsDpi7/4XQ==} + '@rollup/rollup-win32-arm64-msvc@4.21.1': + resolution: {integrity: sha512-3Q3brDgA86gHXWHklrwdREKIrIbxC0ZgU8lwpj0eEKGBQH+31uPqr0P2v11pn0tSIxHvcdOWxa4j+YvLNx1i6g==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.21.0': - resolution: {integrity: sha512-G9+TEqRnAA6nbpqyUqgTiopmnfgnMkR3kMukFBDsiyy23LZvUCpiUwjTRx6ezYCjJODXrh52rBR9oXvm+Fp5wg==} + '@rollup/rollup-win32-ia32-msvc@4.21.1': + resolution: {integrity: sha512-tNg+jJcKR3Uwe4L0/wY3Ro0H+u3nrb04+tcq1GSYzBEmKLeOQF2emk1whxlzNqb6MMrQ2JOcQEpuuiPLyRcSIw==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.21.0': - resolution: {integrity: sha512-2jsCDZwtQvRhejHLfZ1JY6w6kEuEtfF9nzYsZxzSlNVKDX+DpsDJ+Rbjkm74nvg2rdx0gwBS+IMdvwJuq3S9pQ==} + '@rollup/rollup-win32-x64-msvc@4.21.1': + resolution: {integrity: sha512-xGiIH95H1zU7naUyTKEyOA/I0aexNMUdO9qRv0bLKN3qu25bBdrxZHqA3PTJ24YNN/GdMzG4xkDcd/GvjuhfLg==} cpu: [x64] os: [win32] @@ -4714,8 +4723,8 @@ packages: resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} engines: {node: '>=18'} - cjs-module-lexer@1.3.1: - resolution: {integrity: sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==} + cjs-module-lexer@1.4.0: + resolution: {integrity: sha512-N1NGmowPlGBLsOZLPvm48StN04V4YvQRL0i6b7ctrVY3epjP/ct7hFLOItz6pDIvRjwpfPxi52a2UWV2ziir8g==} class-variance-authority@0.7.0: resolution: {integrity: sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A==} @@ -8014,8 +8023,8 @@ packages: robust-predicates@3.0.2: resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==} - rollup@4.21.0: - resolution: {integrity: sha512-vo+S/lfA2lMS7rZ2Qoubi6I5hwZwzXeUIctILZLbHI+laNtvhhOIon2S1JksA5UEDQ7l3vberd0fxK44lTYjbQ==} + rollup@4.21.1: + resolution: {integrity: sha512-ZnYyKvscThhgd3M5+Qt3pmhO4jIRR5RGzaSovB6Q7rGNrK5cUncrtLmcTTJVSdcKXyZjW8X8MB0JMSuH9bcAJg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -10110,6 +10119,8 @@ snapshots: dependencies: '@octokit/openapi-types': 22.2.0 + '@octokit/webhooks-types@7.5.1': {} + '@openapitools/openapi-generator-cli@2.13.5': dependencies: '@nestjs/axios': 3.0.2(@nestjs/common@10.3.0(reflect-metadata@0.1.13)(rxjs@7.8.1))(axios@1.7.4)(rxjs@7.8.1) @@ -11877,52 +11888,52 @@ snapshots: dependencies: '@redis/client': 1.5.17 - '@rollup/rollup-android-arm-eabi@4.21.0': + '@rollup/rollup-android-arm-eabi@4.21.1': optional: true - '@rollup/rollup-android-arm64@4.21.0': + '@rollup/rollup-android-arm64@4.21.1': optional: true - '@rollup/rollup-darwin-arm64@4.21.0': + '@rollup/rollup-darwin-arm64@4.21.1': optional: true - '@rollup/rollup-darwin-x64@4.21.0': + '@rollup/rollup-darwin-x64@4.21.1': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.21.0': + '@rollup/rollup-linux-arm-gnueabihf@4.21.1': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.21.0': + '@rollup/rollup-linux-arm-musleabihf@4.21.1': optional: true - '@rollup/rollup-linux-arm64-gnu@4.21.0': + '@rollup/rollup-linux-arm64-gnu@4.21.1': optional: true - '@rollup/rollup-linux-arm64-musl@4.21.0': + '@rollup/rollup-linux-arm64-musl@4.21.1': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.21.0': + '@rollup/rollup-linux-powerpc64le-gnu@4.21.1': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.21.0': + '@rollup/rollup-linux-riscv64-gnu@4.21.1': optional: true - '@rollup/rollup-linux-s390x-gnu@4.21.0': + '@rollup/rollup-linux-s390x-gnu@4.21.1': optional: true - '@rollup/rollup-linux-x64-gnu@4.21.0': + '@rollup/rollup-linux-x64-gnu@4.21.1': optional: true - '@rollup/rollup-linux-x64-musl@4.21.0': + '@rollup/rollup-linux-x64-musl@4.21.1': optional: true - '@rollup/rollup-win32-arm64-msvc@4.21.0': + '@rollup/rollup-win32-arm64-msvc@4.21.1': optional: true - '@rollup/rollup-win32-ia32-msvc@4.21.0': + '@rollup/rollup-win32-ia32-msvc@4.21.1': optional: true - '@rollup/rollup-win32-x64-msvc@4.21.0': + '@rollup/rollup-win32-x64-msvc@4.21.1': optional: true '@swc/counter@0.1.3': {} @@ -11930,7 +11941,7 @@ snapshots: '@swc/helpers@0.5.5': dependencies: '@swc/counter': 0.1.3 - tslib: 2.6.3 + tslib: 2.6.2 '@t3-oss/env-core@0.10.1(typescript@5.5.3)(zod@3.23.8)': dependencies: @@ -12986,7 +12997,7 @@ snapshots: chownr@3.0.0: {} - cjs-module-lexer@1.3.1: {} + cjs-module-lexer@1.4.0: {} class-variance-authority@0.7.0: dependencies: @@ -14886,7 +14897,7 @@ snapshots: dependencies: acorn: 8.12.1 acorn-import-attributes: 1.9.5(acorn@8.12.1) - cjs-module-lexer: 1.3.1 + cjs-module-lexer: 1.4.0 module-details-from-path: 1.0.3 imurmurhash@0.1.4: {} @@ -16428,7 +16439,7 @@ snapshots: path-scurry@1.10.1: dependencies: lru-cache: 10.3.0 - minipass: 7.0.4 + minipass: 7.1.2 path-to-regexp@0.1.7: {} @@ -17160,26 +17171,26 @@ snapshots: robust-predicates@3.0.2: {} - rollup@4.21.0: + rollup@4.21.1: dependencies: '@types/estree': 1.0.5 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.21.0 - '@rollup/rollup-android-arm64': 4.21.0 - '@rollup/rollup-darwin-arm64': 4.21.0 - '@rollup/rollup-darwin-x64': 4.21.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.21.0 - '@rollup/rollup-linux-arm-musleabihf': 4.21.0 - '@rollup/rollup-linux-arm64-gnu': 4.21.0 - '@rollup/rollup-linux-arm64-musl': 4.21.0 - '@rollup/rollup-linux-powerpc64le-gnu': 4.21.0 - '@rollup/rollup-linux-riscv64-gnu': 4.21.0 - '@rollup/rollup-linux-s390x-gnu': 4.21.0 - '@rollup/rollup-linux-x64-gnu': 4.21.0 - '@rollup/rollup-linux-x64-musl': 4.21.0 - '@rollup/rollup-win32-arm64-msvc': 4.21.0 - '@rollup/rollup-win32-ia32-msvc': 4.21.0 - '@rollup/rollup-win32-x64-msvc': 4.21.0 + '@rollup/rollup-android-arm-eabi': 4.21.1 + '@rollup/rollup-android-arm64': 4.21.1 + '@rollup/rollup-darwin-arm64': 4.21.1 + '@rollup/rollup-darwin-x64': 4.21.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.21.1 + '@rollup/rollup-linux-arm-musleabihf': 4.21.1 + '@rollup/rollup-linux-arm64-gnu': 4.21.1 + '@rollup/rollup-linux-arm64-musl': 4.21.1 + '@rollup/rollup-linux-powerpc64le-gnu': 4.21.1 + '@rollup/rollup-linux-riscv64-gnu': 4.21.1 + '@rollup/rollup-linux-s390x-gnu': 4.21.1 + '@rollup/rollup-linux-x64-gnu': 4.21.1 + '@rollup/rollup-linux-x64-musl': 4.21.1 + '@rollup/rollup-win32-arm64-msvc': 4.21.1 + '@rollup/rollup-win32-ia32-msvc': 4.21.1 + '@rollup/rollup-win32-x64-msvc': 4.21.1 fsevents: 2.3.3 rtl-css-js@1.16.1: @@ -17834,7 +17845,7 @@ snapshots: picocolors: 1.0.1 postcss-load-config: 6.0.1(jiti@1.21.6)(postcss@8.4.39)(tsx@4.16.2) resolve-from: 5.0.0 - rollup: 4.21.0 + rollup: 4.21.1 source-map: 0.8.0-beta.0 sucrase: 3.35.0 tree-kill: 1.2.2