From ed3ee64e4b78ad1b7f2895bd31aeb93a4069d14e Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Wed, 28 Jan 2026 03:04:12 +0100 Subject: [PATCH 01/81] =?UTF-8?q?=E2=9C=A8=20support=20pathname=20in=20log?= =?UTF-8?q?o=20URL=20in=20branding=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 +- messages/en-US.json | 3 + .../loginPage/upsertLoginPageBranding.ts | 31 ++++++-- src/components/AuthPageBrandingForm.tsx | 70 ++++++++++++++----- .../resource-target-address-item.tsx | 1 + src/lib/validateLocalPath.ts | 16 +++++ 6 files changed, 102 insertions(+), 22 deletions(-) create mode 100644 src/lib/validateLocalPath.ts diff --git a/.gitignore b/.gitignore index df9179a4..d2cdfa69 100644 --- a/.gitignore +++ b/.gitignore @@ -51,4 +51,5 @@ dynamic/ scratch/ tsconfig.json hydrateSaas.ts -CLAUDE.md \ No newline at end of file +CLAUDE.md +zaneops.* \ No newline at end of file diff --git a/messages/en-US.json b/messages/en-US.json index f2affe11..46f9092a 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -1877,6 +1877,9 @@ "authPageBrandingQuestionRemove": "Are you sure you want to remove the branding for Auth Pages ?", "authPageBrandingDeleteConfirm": "Confirm Delete Branding", "brandingLogoURL": "Logo URL", + "brandingLogoURLOrPath": "Logo URL or Path", + "brandingLogoPathDescription": "Enter a URL (https://...) or a local path (/logo.png) from the public/ directory on your Pangolin installation.", + "brandingLogoURLDescription": "Enter a publicly accessible URL to your logo image.", "brandingPrimaryColor": "Primary Color", "brandingLogoWidth": "Width (px)", "brandingLogoHeight": "Height (px)", diff --git a/server/private/routers/loginPage/upsertLoginPageBranding.ts b/server/private/routers/loginPage/upsertLoginPageBranding.ts index e6e365be..17f5fbbc 100644 --- a/server/private/routers/loginPage/upsertLoginPageBranding.ts +++ b/server/private/routers/loginPage/upsertLoginPageBranding.ts @@ -29,6 +29,7 @@ import { getOrgTierData } from "#private/lib/billing"; import { TierId } from "@server/lib/billing/tiers"; import { build } from "@server/build"; import config from "@server/private/lib/config"; +import { validateLocalPath } from "@app/lib/validateLocalPath"; const paramsSchema = z.strictObject({ orgId: z.string() @@ -39,14 +40,36 @@ const bodySchema = z.strictObject({ .union([ z.literal(""), z - .url("Must be a valid URL") - .superRefine(async (url, ctx) => { + .string() + .superRefine(async (urlOrPath, ctx) => { + const parseResult = z.url().safeParse(urlOrPath); + if (!parseResult.success) { + if (build !== "enterprise") { + ctx.addIssue({ + code: "custom", + message: "Must be a valid URL" + }); + return; + } else { + try { + validateLocalPath(urlOrPath); + } catch (error) { + ctx.addIssue({ + code: "custom", + message: "Must be either a valid image URL or a valid pathname starting with `/` and not containing query parameters, `..` or `*`" + }); + } finally { + return; + } + } + } + try { - const response = await fetch(url, { + const response = await fetch(urlOrPath, { method: "HEAD" }).catch(() => { // If HEAD fails (CORS or method not allowed), try GET - return fetch(url, { method: "GET" }); + return fetch(urlOrPath, { method: "GET" }); }); if (response.status !== 200) { diff --git a/src/components/AuthPageBrandingForm.tsx b/src/components/AuthPageBrandingForm.tsx index 7bf563f4..1246244b 100644 --- a/src/components/AuthPageBrandingForm.tsx +++ b/src/components/AuthPageBrandingForm.tsx @@ -1,9 +1,5 @@ "use client"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { startTransition, useActionState, useState } from "react"; -import { useForm } from "react-hook-form"; -import z from "zod"; import { Form, FormControl, @@ -13,6 +9,11 @@ import { FormLabel, FormMessage } from "@app/components/ui/form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useTranslations } from "next-intl"; +import { useActionState } from "react"; +import { useForm } from "react-hook-form"; +import z from "zod"; import { SettingsSection, SettingsSectionBody, @@ -21,20 +22,19 @@ import { SettingsSectionHeader, SettingsSectionTitle } from "./Settings"; -import { useTranslations } from "next-intl"; -import type { GetLoginPageBrandingResponse } from "@server/routers/loginPage/types"; -import { Input } from "./ui/input"; -import { ExternalLink, InfoIcon, XIcon } from "lucide-react"; -import { Button } from "./ui/button"; -import { createApiClient, formatAxiosError } from "@app/lib/api"; import { useEnvContext } from "@app/hooks/useEnvContext"; -import { useRouter } from "next/navigation"; -import { toast } from "@app/hooks/useToast"; import { usePaidStatus } from "@app/hooks/usePaidStatus"; +import { toast } from "@app/hooks/useToast"; +import { createApiClient, formatAxiosError } from "@app/lib/api"; import { build } from "@server/build"; +import type { GetLoginPageBrandingResponse } from "@server/routers/loginPage/types"; +import { XIcon } from "lucide-react"; +import { useRouter } from "next/navigation"; import { PaidFeaturesAlert } from "./PaidFeaturesAlert"; -import { Alert, AlertDescription, AlertTitle } from "./ui/alert"; +import { Button } from "./ui/button"; +import { Input } from "./ui/input"; +import { validateLocalPath } from "@app/lib/validateLocalPath"; export type AuthPageCustomizationProps = { orgId: string; @@ -44,13 +44,36 @@ export type AuthPageCustomizationProps = { const AuthPageFormSchema = z.object({ logoUrl: z.union([ z.literal(""), - z.url("Must be a valid URL").superRefine(async (url, ctx) => { + z.string().superRefine(async (urlOrPath, ctx) => { + const parseResult = z.url().safeParse(urlOrPath); + if (!parseResult.success) { + if (build !== "enterprise") { + ctx.addIssue({ + code: "custom", + message: "Must be a valid URL" + }); + return; + } else { + try { + validateLocalPath(urlOrPath); + } catch (error) { + ctx.addIssue({ + code: "custom", + message: + "Must be either a valid image URL or a valid pathname starting with `/` and not containing query parameters, `..` or `*`" + }); + } finally { + return; + } + } + } + try { - const response = await fetch(url, { + const response = await fetch(urlOrPath, { method: "HEAD" }).catch(() => { // If HEAD fails (CORS or method not allowed), try GET - return fetch(url, { method: "GET" }); + return fetch(urlOrPath, { method: "GET" }); }); if (response.status !== 200) { @@ -270,12 +293,25 @@ export default function AuthPageBrandingForm({ render={({ field }) => ( - {t("brandingLogoURL")} + {build === "enterprise" + ? t( + "brandingLogoURLOrPath" + ) + : t("brandingLogoURL")} + + {build === "enterprise" + ? t( + "brandingLogoPathDescription" + ) + : t( + "brandingLogoURLDescription" + )} + )} /> diff --git a/src/components/resource-target-address-item.tsx b/src/components/resource-target-address-item.tsx index 3c4cb927..6479ede7 100644 --- a/src/components/resource-target-address-item.tsx +++ b/src/components/resource-target-address-item.tsx @@ -20,6 +20,7 @@ import { import { Input } from "./ui/input"; import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover"; import { Select, SelectContent, SelectItem, SelectTrigger } from "./ui/select"; +import { useEffect } from "react"; type SiteWithUpdateAvailable = ListSitesResponse["sites"][number]; diff --git a/src/lib/validateLocalPath.ts b/src/lib/validateLocalPath.ts new file mode 100644 index 00000000..7f87eb44 --- /dev/null +++ b/src/lib/validateLocalPath.ts @@ -0,0 +1,16 @@ +export function validateLocalPath(value: string) { + try { + const url = new URL("https://pangoling.net" + value); + if ( + url.pathname !== value || + value.includes("..") || + value.includes("*") + ) { + throw new Error("Invalid Path"); + } + } catch { + throw new Error( + "should be a valid pathname starting with `/` and not containing query parameters, `..` or `*`" + ); + } +} \ No newline at end of file From 38ac4c59805342a9b139d2bf0e9b93ede0897cc4 Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Wed, 28 Jan 2026 04:46:54 +0100 Subject: [PATCH 02/81] =?UTF-8?q?=F0=9F=9A=A7=20wip:=20paginated=20tables?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/routers/site/listSites.ts | 4 +-- src/app/[orgId]/settings/sites/page.tsx | 2 -- src/components/SitesTable.tsx | 47 +++++++++---------------- src/lib/queries.ts | 40 ++++++++++----------- 4 files changed, 36 insertions(+), 57 deletions(-) diff --git a/server/routers/site/listSites.ts b/server/routers/site/listSites.ts index 4fe05c26..68fa05b1 100644 --- a/server/routers/site/listSites.ts +++ b/server/routers/site/listSites.ts @@ -77,7 +77,7 @@ const listSitesSchema = z.object({ limit: z .string() .optional() - .default("1000") + .default("1") .transform(Number) .pipe(z.int().positive()), offset: z @@ -130,7 +130,7 @@ type SiteWithUpdateAvailable = Awaited>[0] & { export type ListSitesResponse = { sites: SiteWithUpdateAvailable[]; - pagination: { total: number; limit: number; offset: number }; + pagination: { total: number; limit: number; offset: number; }; }; registry.registerPath({ diff --git a/src/app/[orgId]/settings/sites/page.tsx b/src/app/[orgId]/settings/sites/page.tsx index 85f0e2b1..877eb594 100644 --- a/src/app/[orgId]/settings/sites/page.tsx +++ b/src/app/[orgId]/settings/sites/page.tsx @@ -60,8 +60,6 @@ export default async function SitesPage(props: SitesPageProps) { return ( <> - {/* */} - (null); const [rows, setRows] = useState(sites); - const [isRefreshing, setIsRefreshing] = useState(false); + const [isRefreshing, startTransition] = useTransition(); const api = createApiClient(useEnvContext()); const t = useTranslations(); - const { env } = useEnvContext(); - - // Update local state when props change (e.g., after refresh) - useEffect(() => { - setRows(sites); - }, [sites]); const refreshData = async () => { - console.log("Data refreshed"); - setIsRefreshing(true); try { - await new Promise((resolve) => setTimeout(resolve, 200)); router.refresh(); } catch (error) { toast({ @@ -84,8 +71,6 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) { description: t("refreshError"), variant: "destructive" }); - } finally { - setIsRefreshing(false); } }; @@ -456,7 +441,7 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) { createSite={() => router.push(`/${orgId}/settings/sites/create`) } - onRefresh={refreshData} + onRefresh={() => startTransition(refreshData)} isRefreshing={isRefreshing} columnVisibility={{ niceId: false, diff --git a/src/lib/queries.ts b/src/lib/queries.ts index f0dfa811..45746ed3 100644 --- a/src/lib/queries.ts +++ b/src/lib/queries.ts @@ -113,7 +113,7 @@ export const orgQueries = { return res.data.data.clients; } }), - users: ({ orgId }: { orgId: string }) => + users: ({ orgId }: { orgId: string; }) => queryOptions({ queryKey: ["ORG", orgId, "USERS"] as const, queryFn: async ({ signal, meta }) => { @@ -124,7 +124,7 @@ export const orgQueries = { return res.data.data.users; } }), - roles: ({ orgId }: { orgId: string }) => + roles: ({ orgId }: { orgId: string; }) => queryOptions({ queryKey: ["ORG", orgId, "ROLES"] as const, queryFn: async ({ signal, meta }) => { @@ -136,7 +136,7 @@ export const orgQueries = { } }), - sites: ({ orgId }: { orgId: string }) => + sites: ({ orgId }: { orgId: string; }) => queryOptions({ queryKey: ["ORG", orgId, "SITES"] as const, queryFn: async ({ signal, meta }) => { @@ -147,7 +147,7 @@ export const orgQueries = { } }), - domains: ({ orgId }: { orgId: string }) => + domains: ({ orgId }: { orgId: string; }) => queryOptions({ queryKey: ["ORG", orgId, "DOMAINS"] as const, queryFn: async ({ signal, meta }) => { @@ -169,7 +169,7 @@ export const orgQueries = { queryFn: async ({ signal, meta }) => { const res = await meta!.api.get< AxiosResponse<{ - idps: { idpId: number; name: string }[]; + idps: { idpId: number; name: string; }[]; }> >( build === "saas" || useOrgOnlyIdp @@ -188,23 +188,19 @@ export const logAnalyticsFiltersSchema = z.object({ .refine((val) => !isNaN(Date.parse(val)), { error: "timeStart must be a valid ISO date string" }) - .optional(), + .optional().catch(undefined), timeEnd: z .string() .refine((val) => !isNaN(Date.parse(val)), { error: "timeEnd must be a valid ISO date string" }) - .optional(), - resourceId: z - .string() - .optional() - .transform(Number) - .pipe(z.int().positive()) - .optional() + .optional().catch(undefined), + resourceId: z.coerce.number().optional().catch(undefined) }); export type LogAnalyticsFilters = z.TypeOf; + export const logQueries = { requestAnalytics: ({ orgId, @@ -234,7 +230,7 @@ export const logQueries = { }; export const resourceQueries = { - resourceUsers: ({ resourceId }: { resourceId: number }) => + resourceUsers: ({ resourceId }: { resourceId: number; }) => queryOptions({ queryKey: ["RESOURCES", resourceId, "USERS"] as const, queryFn: async ({ signal, meta }) => { @@ -244,7 +240,7 @@ export const resourceQueries = { return res.data.data.users; } }), - resourceRoles: ({ resourceId }: { resourceId: number }) => + resourceRoles: ({ resourceId }: { resourceId: number; }) => queryOptions({ queryKey: ["RESOURCES", resourceId, "ROLES"] as const, queryFn: async ({ signal, meta }) => { @@ -255,7 +251,7 @@ export const resourceQueries = { return res.data.data.roles; } }), - siteResourceUsers: ({ siteResourceId }: { siteResourceId: number }) => + siteResourceUsers: ({ siteResourceId }: { siteResourceId: number; }) => queryOptions({ queryKey: ["SITE_RESOURCES", siteResourceId, "USERS"] as const, queryFn: async ({ signal, meta }) => { @@ -265,7 +261,7 @@ export const resourceQueries = { return res.data.data.users; } }), - siteResourceRoles: ({ siteResourceId }: { siteResourceId: number }) => + siteResourceRoles: ({ siteResourceId }: { siteResourceId: number; }) => queryOptions({ queryKey: ["SITE_RESOURCES", siteResourceId, "ROLES"] as const, queryFn: async ({ signal, meta }) => { @@ -276,7 +272,7 @@ export const resourceQueries = { return res.data.data.roles; } }), - siteResourceClients: ({ siteResourceId }: { siteResourceId: number }) => + siteResourceClients: ({ siteResourceId }: { siteResourceId: number; }) => queryOptions({ queryKey: ["SITE_RESOURCES", siteResourceId, "CLIENTS"] as const, queryFn: async ({ signal, meta }) => { @@ -287,7 +283,7 @@ export const resourceQueries = { return res.data.data.clients; } }), - resourceTargets: ({ resourceId }: { resourceId: number }) => + resourceTargets: ({ resourceId }: { resourceId: number; }) => queryOptions({ queryKey: ["RESOURCES", resourceId, "TARGETS"] as const, queryFn: async ({ signal, meta }) => { @@ -298,7 +294,7 @@ export const resourceQueries = { return res.data.data.targets; } }), - resourceWhitelist: ({ resourceId }: { resourceId: number }) => + resourceWhitelist: ({ resourceId }: { resourceId: number; }) => queryOptions({ queryKey: ["RESOURCES", resourceId, "WHITELISTS"] as const, queryFn: async ({ signal, meta }) => { @@ -371,7 +367,7 @@ export const approvalQueries = { } const res = await meta!.api.get< - AxiosResponse<{ approvals: ApprovalItem[] }> + AxiosResponse<{ approvals: ApprovalItem[]; }> >(`/org/${orgId}/approvals?${sp.toString()}`, { signal }); @@ -383,7 +379,7 @@ export const approvalQueries = { queryKey: ["APPROVALS", orgId, "COUNT", "pending"] as const, queryFn: async ({ signal, meta }) => { const res = await meta!.api.get< - AxiosResponse<{ count: number }> + AxiosResponse<{ count: number; }> >(`/org/${orgId}/approvals/count?approvalState=pending`, { signal }); From c89c1a03da759aa675b2c15f968e2e74d39afa9d Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Thu, 29 Jan 2026 05:05:34 +0100 Subject: [PATCH 03/81] =?UTF-8?q?=F0=9F=8E=A8=20use=20prettier=20for=20for?= =?UTF-8?q?matting=20typescript?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 767e57b5..5092cb6c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,7 +10,7 @@ "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[typescript]": { - "editor.defaultFormatter": "vscode.typescript-language-features" + "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[typescriptreact]": { "editor.defaultFormatter": "esbenp.prettier-vscode" From 01a2820390847edec61898203687092ab6d6c5bf Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Thu, 29 Jan 2026 05:07:27 +0100 Subject: [PATCH 04/81] =?UTF-8?q?=F0=9F=9A=A7=20POC:=20pagination=20in=20s?= =?UTF-8?q?ites=20table?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/routers/site/listSites.ts | 34 ++--- src/app/[orgId]/settings/sites/page.tsx | 27 +++- src/components/SitesDataTable.tsx | 50 -------- src/components/SitesTable.tsx | 66 +++++++--- src/components/ui/data-table.tsx | 158 ++++++++++++++++-------- src/lib/queries.ts | 35 +++--- 6 files changed, 217 insertions(+), 153 deletions(-) delete mode 100644 src/components/SitesDataTable.tsx diff --git a/server/routers/site/listSites.ts b/server/routers/site/listSites.ts index 68fa05b1..dab79c8d 100644 --- a/server/routers/site/listSites.ts +++ b/server/routers/site/listSites.ts @@ -74,18 +74,20 @@ const listSitesParamsSchema = z.strictObject({ }); const listSitesSchema = z.object({ - limit: z - .string() + pageSize: z.coerce + .number() // for prettier formatting + .int() + .positive() .optional() - .default("1") - .transform(Number) - .pipe(z.int().positive()), - offset: z - .string() + .catch(20) + .default(20), + page: z.coerce + .number() // for prettier formatting + .int() + .min(0) .optional() - .default("0") - .transform(Number) - .pipe(z.int().nonnegative()) + .catch(1) + .default(1) }); function querySites(orgId: string, accessibleSiteIds: number[]) { @@ -130,7 +132,7 @@ type SiteWithUpdateAvailable = Awaited>[0] & { export type ListSitesResponse = { sites: SiteWithUpdateAvailable[]; - pagination: { total: number; limit: number; offset: number; }; + pagination: { total: number; pageSize: number; page: number }; }; registry.registerPath({ @@ -160,7 +162,7 @@ export async function listSites( ) ); } - const { limit, offset } = parsedQuery.data; + const { pageSize, page } = parsedQuery.data; const parsedParams = listSitesParamsSchema.safeParse(req.params); if (!parsedParams.success) { @@ -216,7 +218,9 @@ export async function listSites( ) ); - const sitesList = await baseQuery.limit(limit).offset(offset); + const sitesList = await baseQuery + .limit(pageSize) + .offset(pageSize * (page - 1)); const totalCountResult = await countQuery; const totalCount = totalCountResult[0].count; @@ -267,8 +271,8 @@ export async function listSites( sites: sitesWithUpdates, pagination: { total: totalCount, - limit, - offset + pageSize, + page } }, success: true, diff --git a/src/app/[orgId]/settings/sites/page.tsx b/src/app/[orgId]/settings/sites/page.tsx index 877eb594..69bb599c 100644 --- a/src/app/[orgId]/settings/sites/page.tsx +++ b/src/app/[orgId]/settings/sites/page.tsx @@ -9,19 +9,30 @@ import { getTranslations } from "next-intl/server"; type SitesPageProps = { params: Promise<{ orgId: string }>; + searchParams: Promise>; }; export const dynamic = "force-dynamic"; export default async function SitesPage(props: SitesPageProps) { const params = await props.params; + + const searchParams = new URLSearchParams(await props.searchParams); + let sites: ListSitesResponse["sites"] = []; + let pagination: ListSitesResponse["pagination"] = { + total: 0, + page: 1, + pageSize: 20 + }; try { const res = await internal.get>( - `/org/${params.orgId}/sites`, + `/org/${params.orgId}/sites?${searchParams.toString()}`, await authCookieHeader() ); - sites = res.data.data.sites; + const responseData = res.data.data; + sites = responseData.sites; + pagination = responseData.pagination; } catch (e) {} const t = await getTranslations(); @@ -67,7 +78,17 @@ export default async function SitesPage(props: SitesPageProps) { - + ); } diff --git a/src/components/SitesDataTable.tsx b/src/components/SitesDataTable.tsx deleted file mode 100644 index 125f4d59..00000000 --- a/src/components/SitesDataTable.tsx +++ /dev/null @@ -1,50 +0,0 @@ -"use client"; - -import { ColumnDef } from "@tanstack/react-table"; -import { DataTable } from "@app/components/ui/data-table"; -import { useTranslations } from "next-intl"; - -interface DataTableProps { - columns: ColumnDef[]; - data: TData[]; - createSite?: () => void; - onRefresh?: () => void; - isRefreshing?: boolean; - columnVisibility?: Record; - enableColumnVisibility?: boolean; -} - -export function SitesDataTable({ - columns, - data, - createSite, - onRefresh, - isRefreshing, - columnVisibility, - enableColumnVisibility -}: DataTableProps) { - const t = useTranslations(); - - return ( - - ); -} diff --git a/src/components/SitesTable.tsx b/src/components/SitesTable.tsx index 58c2366b..497715b1 100644 --- a/src/components/SitesTable.tsx +++ b/src/components/SitesTable.tsx @@ -1,10 +1,14 @@ "use client"; import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog"; -import { SitesDataTable } from "@app/components/SitesDataTable"; + import { Badge } from "@app/components/ui/badge"; import { Button } from "@app/components/ui/button"; -import { ExtendedColumnDef } from "@app/components/ui/data-table"; +import { + DataTable, + ExtendedColumnDef, + type DataTablePaginationState +} from "@app/components/ui/data-table"; import { DropdownMenu, DropdownMenuContent, @@ -26,7 +30,7 @@ import { } from "lucide-react"; import { useTranslations } from "next-intl"; import Link from "next/link"; -import { useRouter } from "next/navigation"; +import { usePathname, useRouter, useSearchParams } from "next/navigation"; import { useEffect, useState, useTransition } from "react"; export type SiteRow = { @@ -48,15 +52,21 @@ export type SiteRow = { type SitesTableProps = { sites: SiteRow[]; + pagination: DataTablePaginationState; orgId: string; }; -export default function SitesTable({ sites, orgId }: SitesTableProps) { +export default function SitesTable({ + sites, + orgId, + pagination +}: SitesTableProps) { const router = useRouter(); + const searchParams = useSearchParams(); + const pathname = usePathname(); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const [selectedSite, setSelectedSite] = useState(null); - const [rows, setRows] = useState(sites); const [isRefreshing, startTransition] = useTransition(); const api = createApiClient(useEnvContext()); @@ -87,10 +97,6 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) { .then(() => { router.refresh(); setIsDeleteModalOpen(false); - - const newRows = rows.filter((row) => row.id !== siteId); - - setRows(newRows); }); }; @@ -413,6 +419,11 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) { } ]; + console.log({ + sites, + pagination + }); + return ( <> {selectedSite && ( @@ -429,27 +440,50 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) { } buttonText={t("siteConfirmDelete")} - onConfirm={async () => deleteSite(selectedSite!.id)} + onConfirm={async () => + startTransition(() => deleteSite(selectedSite!.id)) + } string={selectedSite.name} title={t("siteDelete")} /> )} - - router.push(`/${orgId}/settings/sites/create`) - } + data={sites} + persistPageSize="sites-table" + title={t("sites")} + searchPlaceholder={t("searchSitesProgress")} + manualFiltering + pagination={pagination} + onPaginationChange={(newPage) => { + console.log({ + newPage + }); + const sp = new URLSearchParams(searchParams); + sp.set("page", (newPage.pageIndex + 1).toString()); + sp.set("pageSize", newPage.pageSize.toString()); + startTransition(() => + router.push(`${pathname}?${sp.toString()}`) + ); + }} + onAdd={() => router.push(`/${orgId}/settings/sites/create`)} + addButtonText={t("siteAdd")} onRefresh={() => startTransition(refreshData)} isRefreshing={isRefreshing} + defaultSort={{ + id: "name", + desc: false + }} columnVisibility={{ niceId: false, nice: false, exitNode: false, address: false }} - enableColumnVisibility={true} + enableColumnVisibility + stickyLeftColumn="name" + stickyRightColumn="actions" /> ); diff --git a/src/components/ui/data-table.tsx b/src/components/ui/data-table.tsx index af61bb53..bb350577 100644 --- a/src/components/ui/data-table.tsx +++ b/src/components/ui/data-table.tsx @@ -151,11 +151,20 @@ type DataTableFilter = { label: string; options: FilterOption[]; multiSelect?: boolean; - filterFn: (row: any, selectedValues: (string | number | boolean)[]) => boolean; + filterFn: ( + row: any, + selectedValues: (string | number | boolean)[] + ) => boolean; defaultValues?: (string | number | boolean)[]; displayMode?: "label" | "calculated"; // How to display the filter button text }; +export type DataTablePaginationState = PaginationState & { + pageCount: number; +}; + +export type DataTablePaginationUpdateFn = (newPage: PaginationState) => void; + type DataTableProps = { columns: ExtendedColumnDef[]; data: TData[]; @@ -178,6 +187,11 @@ type DataTableProps = { defaultPageSize?: number; columnVisibility?: Record; enableColumnVisibility?: boolean; + manualFiltering?: boolean; + onSearch?: (input: string) => void; + searchValue?: string; + pagination?: DataTablePaginationState; + onPaginationChange?: DataTablePaginationUpdateFn; persistColumnVisibility?: boolean | string; stickyLeftColumn?: string; // Column ID or accessorKey for left sticky column stickyRightColumn?: string; // Column ID or accessorKey for right sticky column (typically "actions") @@ -203,7 +217,12 @@ export function DataTable({ columnVisibility: defaultColumnVisibility, enableColumnVisibility = false, persistColumnVisibility = false, + manualFiltering = false, + pagination: paginationState, stickyLeftColumn, + onSearch, + searchValue, + onPaginationChange, stickyRightColumn }: DataTableProps) { const t = useTranslations(); @@ -248,22 +267,25 @@ export function DataTable({ const [columnVisibility, setColumnVisibility] = useState( initialColumnVisibility ); - const [pagination, setPagination] = useState({ + const [_pagination, setPagination] = useState({ pageIndex: 0, pageSize: pageSize }); + + const pagination = paginationState ?? _pagination; + const [activeTab, setActiveTab] = useState( defaultTab || tabs?.[0]?.id || "" ); - const [activeFilters, setActiveFilters] = useState>( - () => { - const initial: Record = {}; - filters?.forEach((filter) => { - initial[filter.id] = filter.defaultValues || []; - }); - return initial; - } - ); + const [activeFilters, setActiveFilters] = useState< + Record + >(() => { + const initial: Record = {}; + filters?.forEach((filter) => { + initial[filter.id] = filter.defaultValues || []; + }); + return initial; + }); // Track initial values to avoid storing defaults on first render const initialPageSize = useRef(pageSize); @@ -309,7 +331,16 @@ export function DataTable({ getFilteredRowModel: getFilteredRowModel(), onGlobalFilterChange: setGlobalFilter, onColumnVisibilityChange: setColumnVisibility, - onPaginationChange: setPagination, + onPaginationChange: onPaginationChange + ? (state) => { + const newState = + typeof state === "function" ? state(pagination) : state; + onPaginationChange(newState); + } + : setPagination, + manualFiltering, + manualPagination: !!paginationState, + pageCount: paginationState?.pageCount, initialState: { pagination: { pageSize: pageSize, @@ -368,11 +399,11 @@ export function DataTable({ setActiveFilters((prev) => { const currentValues = prev[filterId] || []; const filter = filters?.find((f) => f.id === filterId); - + if (!filter) return prev; let newValues: (string | number | boolean)[]; - + if (filter.multiSelect) { // Multi-select: add or remove the value if (checked) { @@ -397,7 +428,7 @@ export function DataTable({ // Calculate display text for a filter based on selected values const getFilterDisplayText = (filter: DataTableFilter): string => { const selectedValues = activeFilters[filter.id] || []; - + if (selectedValues.length === 0) { return filter.label; } @@ -477,12 +508,14 @@ export function DataTable({
- table.setGlobalFilter( - String(e.target.value) - ) - } + value={searchValue ?? globalFilter ?? ""} + onChange={(e) => { + onSearch + ? onSearch(e.currentTarget.value) + : table.setGlobalFilter( + String(e.target.value) + ); + }} className="w-full pl-8" /> @@ -490,13 +523,17 @@ export function DataTable({ {filters && filters.length > 0 && (
{filters.map((filter) => { - const selectedValues = activeFilters[filter.id] || []; - const hasActiveFilters = selectedValues.length > 0; - const displayMode = filter.displayMode || filterDisplayMode; - const displayText = displayMode === "calculated" - ? getFilterDisplayText(filter) - : filter.label; - + const selectedValues = + activeFilters[filter.id] || []; + const hasActiveFilters = + selectedValues.length > 0; + const displayMode = + filter.displayMode || filterDisplayMode; + const displayText = + displayMode === "calculated" + ? getFilterDisplayText(filter) + : filter.label; + return ( @@ -507,37 +544,54 @@ export function DataTable({ > {displayText} - {displayMode === "label" && hasActiveFilters && ( - - {selectedValues.length} - - )} + {displayMode === "label" && + hasActiveFilters && ( + + { + selectedValues.length + } + + )} - + {filter.label} - {filter.options.map((option) => { - const isChecked = selectedValues.includes(option.value); - return ( - - handleFilterChange( - filter.id, - option.value, + {filter.options.map( + (option) => { + const isChecked = + selectedValues.includes( + option.value + ); + return ( + e.preventDefault()} - > - {option.label} - - ); - })} + ) => + handleFilterChange( + filter.id, + option.value, + checked + ) + } + onSelect={(e) => + e.preventDefault() + } + > + {option.label} + + ); + } + )} ); diff --git a/src/lib/queries.ts b/src/lib/queries.ts index 45746ed3..6c8e67c0 100644 --- a/src/lib/queries.ts +++ b/src/lib/queries.ts @@ -113,7 +113,7 @@ export const orgQueries = { return res.data.data.clients; } }), - users: ({ orgId }: { orgId: string; }) => + users: ({ orgId }: { orgId: string }) => queryOptions({ queryKey: ["ORG", orgId, "USERS"] as const, queryFn: async ({ signal, meta }) => { @@ -124,7 +124,7 @@ export const orgQueries = { return res.data.data.users; } }), - roles: ({ orgId }: { orgId: string; }) => + roles: ({ orgId }: { orgId: string }) => queryOptions({ queryKey: ["ORG", orgId, "ROLES"] as const, queryFn: async ({ signal, meta }) => { @@ -136,7 +136,7 @@ export const orgQueries = { } }), - sites: ({ orgId }: { orgId: string; }) => + sites: ({ orgId }: { orgId: string }) => queryOptions({ queryKey: ["ORG", orgId, "SITES"] as const, queryFn: async ({ signal, meta }) => { @@ -147,7 +147,7 @@ export const orgQueries = { } }), - domains: ({ orgId }: { orgId: string; }) => + domains: ({ orgId }: { orgId: string }) => queryOptions({ queryKey: ["ORG", orgId, "DOMAINS"] as const, queryFn: async ({ signal, meta }) => { @@ -169,7 +169,7 @@ export const orgQueries = { queryFn: async ({ signal, meta }) => { const res = await meta!.api.get< AxiosResponse<{ - idps: { idpId: number; name: string; }[]; + idps: { idpId: number; name: string }[]; }> >( build === "saas" || useOrgOnlyIdp @@ -188,19 +188,20 @@ export const logAnalyticsFiltersSchema = z.object({ .refine((val) => !isNaN(Date.parse(val)), { error: "timeStart must be a valid ISO date string" }) - .optional().catch(undefined), + .optional() + .catch(undefined), timeEnd: z .string() .refine((val) => !isNaN(Date.parse(val)), { error: "timeEnd must be a valid ISO date string" }) - .optional().catch(undefined), + .optional() + .catch(undefined), resourceId: z.coerce.number().optional().catch(undefined) }); export type LogAnalyticsFilters = z.TypeOf; - export const logQueries = { requestAnalytics: ({ orgId, @@ -230,7 +231,7 @@ export const logQueries = { }; export const resourceQueries = { - resourceUsers: ({ resourceId }: { resourceId: number; }) => + resourceUsers: ({ resourceId }: { resourceId: number }) => queryOptions({ queryKey: ["RESOURCES", resourceId, "USERS"] as const, queryFn: async ({ signal, meta }) => { @@ -240,7 +241,7 @@ export const resourceQueries = { return res.data.data.users; } }), - resourceRoles: ({ resourceId }: { resourceId: number; }) => + resourceRoles: ({ resourceId }: { resourceId: number }) => queryOptions({ queryKey: ["RESOURCES", resourceId, "ROLES"] as const, queryFn: async ({ signal, meta }) => { @@ -251,7 +252,7 @@ export const resourceQueries = { return res.data.data.roles; } }), - siteResourceUsers: ({ siteResourceId }: { siteResourceId: number; }) => + siteResourceUsers: ({ siteResourceId }: { siteResourceId: number }) => queryOptions({ queryKey: ["SITE_RESOURCES", siteResourceId, "USERS"] as const, queryFn: async ({ signal, meta }) => { @@ -261,7 +262,7 @@ export const resourceQueries = { return res.data.data.users; } }), - siteResourceRoles: ({ siteResourceId }: { siteResourceId: number; }) => + siteResourceRoles: ({ siteResourceId }: { siteResourceId: number }) => queryOptions({ queryKey: ["SITE_RESOURCES", siteResourceId, "ROLES"] as const, queryFn: async ({ signal, meta }) => { @@ -272,7 +273,7 @@ export const resourceQueries = { return res.data.data.roles; } }), - siteResourceClients: ({ siteResourceId }: { siteResourceId: number; }) => + siteResourceClients: ({ siteResourceId }: { siteResourceId: number }) => queryOptions({ queryKey: ["SITE_RESOURCES", siteResourceId, "CLIENTS"] as const, queryFn: async ({ signal, meta }) => { @@ -283,7 +284,7 @@ export const resourceQueries = { return res.data.data.clients; } }), - resourceTargets: ({ resourceId }: { resourceId: number; }) => + resourceTargets: ({ resourceId }: { resourceId: number }) => queryOptions({ queryKey: ["RESOURCES", resourceId, "TARGETS"] as const, queryFn: async ({ signal, meta }) => { @@ -294,7 +295,7 @@ export const resourceQueries = { return res.data.data.targets; } }), - resourceWhitelist: ({ resourceId }: { resourceId: number; }) => + resourceWhitelist: ({ resourceId }: { resourceId: number }) => queryOptions({ queryKey: ["RESOURCES", resourceId, "WHITELISTS"] as const, queryFn: async ({ signal, meta }) => { @@ -367,7 +368,7 @@ export const approvalQueries = { } const res = await meta!.api.get< - AxiosResponse<{ approvals: ApprovalItem[]; }> + AxiosResponse<{ approvals: ApprovalItem[] }> >(`/org/${orgId}/approvals?${sp.toString()}`, { signal }); @@ -379,7 +380,7 @@ export const approvalQueries = { queryKey: ["APPROVALS", orgId, "COUNT", "pending"] as const, queryFn: async ({ signal, meta }) => { const res = await meta!.api.get< - AxiosResponse<{ count: number; }> + AxiosResponse<{ count: number }> >(`/org/${orgId}/approvals/count?approvalState=pending`, { signal }); From d374ea6ea655371493e92bb07c6b885df8559ad9 Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Thu, 29 Jan 2026 05:07:41 +0100 Subject: [PATCH 05/81] =?UTF-8?q?=F0=9F=9A=A7wip?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/SitesTable.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/components/SitesTable.tsx b/src/components/SitesTable.tsx index 497715b1..77698a2e 100644 --- a/src/components/SitesTable.tsx +++ b/src/components/SitesTable.tsx @@ -457,9 +457,6 @@ export default function SitesTable({ manualFiltering pagination={pagination} onPaginationChange={(newPage) => { - console.log({ - newPage - }); const sp = new URLSearchParams(searchParams); sp.set("page", (newPage.pageIndex + 1).toString()); sp.set("pageSize", newPage.pageSize.toString()); From b04385a3404267ae6b2cbc30b87623a0a0cb13b8 Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Thu, 29 Jan 2026 05:48:41 +0100 Subject: [PATCH 06/81] =?UTF-8?q?=F0=9F=9A=A7=20search=20on=20table?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 14 ++++++++++++- package.json | 3 ++- server/routers/site/listSites.ts | 36 ++++++++++++++++++++++---------- src/components/SitesTable.tsx | 34 +++++++++++++++++------------- src/components/ui/data-table.tsx | 7 ++++--- 5 files changed, 64 insertions(+), 30 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4a01c8c5..af864a57 100644 --- a/package-lock.json +++ b/package-lock.json @@ -104,6 +104,7 @@ "tailwind-merge": "3.4.0", "topojson-client": "3.1.0", "tw-animate-css": "1.4.0", + "use-debounce": "^10.1.0", "uuid": "13.0.0", "vaul": "1.1.2", "visionscarto-world-atlas": "1.0.0", @@ -13944,7 +13945,6 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "license": "MIT", - "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -23240,6 +23240,18 @@ } } }, + "node_modules/use-debounce": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/use-debounce/-/use-debounce-10.1.0.tgz", + "integrity": "sha512-lu87Za35V3n/MyMoEpD5zJv0k7hCn0p+V/fK2kWD+3k2u3kOCwO593UArbczg1fhfs2rqPEnHpULJ3KmGdDzvg==", + "license": "MIT", + "engines": { + "node": ">= 16.0.0" + }, + "peerDependencies": { + "react": "*" + } + }, "node_modules/use-intl": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/use-intl/-/use-intl-4.7.0.tgz", diff --git a/package.json b/package.json index 25d94c4d..5de7629d 100644 --- a/package.json +++ b/package.json @@ -128,6 +128,7 @@ "tailwind-merge": "3.4.0", "topojson-client": "3.1.0", "tw-animate-css": "1.4.0", + "use-debounce": "^10.1.0", "uuid": "13.0.0", "vaul": "1.1.2", "visionscarto-world-atlas": "1.0.0", @@ -152,6 +153,7 @@ "@types/express": "5.0.6", "@types/express-session": "1.18.2", "@types/jmespath": "0.15.2", + "@types/js-yaml": "4.0.9", "@types/jsonwebtoken": "9.0.10", "@types/node": "24.10.2", "@types/nodemailer": "7.0.4", @@ -164,7 +166,6 @@ "@types/topojson-client": "3.1.5", "@types/ws": "8.18.1", "@types/yargs": "17.0.35", - "@types/js-yaml": "4.0.9", "babel-plugin-react-compiler": "1.0.0", "drizzle-kit": "0.31.8", "esbuild": "0.27.2", diff --git a/server/routers/site/listSites.ts b/server/routers/site/listSites.ts index dab79c8d..cefaeaf3 100644 --- a/server/routers/site/listSites.ts +++ b/server/routers/site/listSites.ts @@ -4,7 +4,7 @@ import { remoteExitNodes } from "@server/db"; import logger from "@server/logger"; import HttpCode from "@server/types/HttpCode"; import response from "@server/lib/response"; -import { and, count, eq, inArray, or, sql } from "drizzle-orm"; +import { and, count, eq, ilike, inArray, or, sql } from "drizzle-orm"; import { NextFunction, Request, Response } from "express"; import createHttpError from "http-errors"; import { z } from "zod"; @@ -87,10 +87,29 @@ const listSitesSchema = z.object({ .min(0) .optional() .catch(1) - .default(1) + .default(1), + query: z.string().optional() }); -function querySites(orgId: string, accessibleSiteIds: number[]) { +function querySites( + orgId: string, + accessibleSiteIds: number[], + query: string = "" +) { + let conditions = and( + inArray(sites.siteId, accessibleSiteIds), + eq(sites.orgId, orgId) + ); + + if (query) { + conditions = and( + conditions, + or( + ilike(sites.name, "%" + query + "%"), + ilike(sites.niceId, "%" + query + "%") + ) + ); + } return db .select({ siteId: sites.siteId, @@ -118,12 +137,7 @@ function querySites(orgId: string, accessibleSiteIds: number[]) { remoteExitNodes, eq(remoteExitNodes.exitNodeId, sites.exitNodeId) ) - .where( - and( - inArray(sites.siteId, accessibleSiteIds), - eq(sites.orgId, orgId) - ) - ); + .where(conditions); } type SiteWithUpdateAvailable = Awaited>[0] & { @@ -162,7 +176,7 @@ export async function listSites( ) ); } - const { pageSize, page } = parsedQuery.data; + const { pageSize, page, query } = parsedQuery.data; const parsedParams = listSitesParamsSchema.safeParse(req.params); if (!parsedParams.success) { @@ -206,7 +220,7 @@ export async function listSites( } const accessibleSiteIds = accessibleSites.map((site) => site.siteId); - const baseQuery = querySites(orgId, accessibleSiteIds); + const baseQuery = querySites(orgId, accessibleSiteIds, query); const countQuery = db .select({ count: count() }) diff --git a/src/components/SitesTable.tsx b/src/components/SitesTable.tsx index 77698a2e..5cbc92f6 100644 --- a/src/components/SitesTable.tsx +++ b/src/components/SitesTable.tsx @@ -21,7 +21,7 @@ import { toast } from "@app/hooks/useToast"; import { createApiClient, formatAxiosError } from "@app/lib/api"; import { parseDataSize } from "@app/lib/dataSize"; import { build } from "@server/build"; -import { Column } from "@tanstack/react-table"; +import { Column, type PaginationState } from "@tanstack/react-table"; import { ArrowRight, ArrowUpDown, @@ -31,7 +31,8 @@ import { import { useTranslations } from "next-intl"; import Link from "next/link"; import { usePathname, useRouter, useSearchParams } from "next/navigation"; -import { useEffect, useState, useTransition } from "react"; +import { useState, useTransition } from "react"; +import { useDebouncedCallback } from "use-debounce"; export type SiteRow = { id: number; @@ -419,10 +420,20 @@ export default function SitesTable({ } ]; - console.log({ - sites, - pagination - }); + const handlePaginationChange = (newPage: PaginationState) => { + const sp = new URLSearchParams(searchParams); + sp.set("page", (newPage.pageIndex + 1).toString()); + sp.set("pageSize", newPage.pageSize.toString()); + startTransition(() => router.push(`${pathname}?${sp.toString()}`)); + }; + + // const = useDebouncedCallback() + + const handleSearchChange = useDebouncedCallback((query: string) => { + const sp = new URLSearchParams(searchParams); + sp.set("query", query); + startTransition(() => router.push(`${pathname}?${sp.toString()}`)); + }, 300); return ( <> @@ -456,15 +467,10 @@ export default function SitesTable({ searchPlaceholder={t("searchSitesProgress")} manualFiltering pagination={pagination} - onPaginationChange={(newPage) => { - const sp = new URLSearchParams(searchParams); - sp.set("page", (newPage.pageIndex + 1).toString()); - sp.set("pageSize", newPage.pageSize.toString()); - startTransition(() => - router.push(`${pathname}?${sp.toString()}`) - ); - }} + onPaginationChange={handlePaginationChange} onAdd={() => router.push(`/${orgId}/settings/sites/create`)} + searchQuery={searchParams.get("query")?.toString()} + onSearch={handleSearchChange} addButtonText={t("siteAdd")} onRefresh={() => startTransition(refreshData)} isRefreshing={isRefreshing} diff --git a/src/components/ui/data-table.tsx b/src/components/ui/data-table.tsx index bb350577..63e75f93 100644 --- a/src/components/ui/data-table.tsx +++ b/src/components/ui/data-table.tsx @@ -189,7 +189,7 @@ type DataTableProps = { enableColumnVisibility?: boolean; manualFiltering?: boolean; onSearch?: (input: string) => void; - searchValue?: string; + searchQuery?: string; pagination?: DataTablePaginationState; onPaginationChange?: DataTablePaginationUpdateFn; persistColumnVisibility?: boolean | string; @@ -221,7 +221,7 @@ export function DataTable({ pagination: paginationState, stickyLeftColumn, onSearch, - searchValue, + searchQuery, onPaginationChange, stickyRightColumn }: DataTableProps) { @@ -508,7 +508,8 @@ export function DataTable({
{ onSearch ? onSearch(e.currentTarget.value) From 89695df0129f5e72c2bc2d410dce2066012b938c Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Fri, 30 Jan 2026 05:39:01 +0100 Subject: [PATCH 07/81] =?UTF-8?q?=F0=9F=9A=A7=20wip:=20pagination=20and=20?= =?UTF-8?q?search=20work?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/routers/site/listSites.ts | 21 +- src/app/[orgId]/settings/sites/page.tsx | 4 +- src/components/SitesTable.tsx | 36 +- src/components/UserDevicesTable.tsx | 89 ++-- src/components/ui/data-table.tsx | 12 +- src/components/ui/manual-data-table.tsx | 567 ++++++++++++++++++++++++ 6 files changed, 667 insertions(+), 62 deletions(-) create mode 100644 src/components/ui/manual-data-table.tsx diff --git a/server/routers/site/listSites.ts b/server/routers/site/listSites.ts index cefaeaf3..e77f6333 100644 --- a/server/routers/site/listSites.ts +++ b/server/routers/site/listSites.ts @@ -222,15 +222,24 @@ export async function listSites( const accessibleSiteIds = accessibleSites.map((site) => site.siteId); const baseQuery = querySites(orgId, accessibleSiteIds, query); + let conditions = and( + inArray(sites.siteId, accessibleSiteIds), + eq(sites.orgId, orgId) + ); + if (query) { + conditions = and( + conditions, + or( + ilike(sites.name, "%" + query + "%"), + ilike(sites.niceId, "%" + query + "%") + ) + ); + } + const countQuery = db .select({ count: count() }) .from(sites) - .where( - and( - inArray(sites.siteId, accessibleSiteIds), - eq(sites.orgId, orgId) - ) - ); + .where(conditions); const sitesList = await baseQuery .limit(pageSize) diff --git a/src/app/[orgId]/settings/sites/page.tsx b/src/app/[orgId]/settings/sites/page.tsx index 69bb599c..161c757f 100644 --- a/src/app/[orgId]/settings/sites/page.tsx +++ b/src/app/[orgId]/settings/sites/page.tsx @@ -81,10 +81,8 @@ export default async function SitesPage(props: SitesPageProps) { router.push(`${pathname}?${sp.toString()}`)); }; - // const = useDebouncedCallback() - const handleSearchChange = useDebouncedCallback((query: string) => { const sp = new URLSearchParams(searchParams); sp.set("query", query); + sp.delete("page"); startTransition(() => router.push(`${pathname}?${sp.toString()}`)); }, 300); + console.log({ + pagination, + rowCount + }); + return ( <> {selectedSite && ( @@ -459,13 +464,11 @@ export default function SitesTable({ /> )} - router.push(`/${orgId}/settings/sites/create`)} @@ -474,10 +477,7 @@ export default function SitesTable({ addButtonText={t("siteAdd")} onRefresh={() => startTransition(refreshData)} isRefreshing={isRefreshing} - defaultSort={{ - id: "name", - desc: false - }} + rowCount={rowCount} columnVisibility={{ niceId: false, nice: false, diff --git a/src/components/UserDevicesTable.tsx b/src/components/UserDevicesTable.tsx index 9d1469f1..edc84088 100644 --- a/src/components/UserDevicesTable.tsx +++ b/src/components/UserDevicesTable.tsx @@ -13,7 +13,10 @@ import { useEnvContext } from "@app/hooks/useEnvContext"; import { toast } from "@app/hooks/useToast"; import { createApiClient, formatAxiosError } from "@app/lib/api"; import { getUserDisplayName } from "@app/lib/getUserDisplayName"; -import { formatFingerprintInfo, formatPlatform } from "@app/lib/formatDeviceFingerprint"; +import { + formatFingerprintInfo, + formatPlatform +} from "@app/lib/formatDeviceFingerprint"; import { ArrowRight, ArrowUpDown, @@ -188,9 +191,13 @@ export default function UserDevicesTable({ userClients }: ClientTableProps) { try { // Fetch approvalId for this client using clientId query parameter const approvalsRes = await api.get<{ - data: { approvals: Array<{ approvalId: number; clientId: number }> }; - }>(`/org/${clientRow.orgId}/approvals?approvalState=pending&clientId=${clientRow.id}`); - + data: { + approvals: Array<{ approvalId: number; clientId: number }>; + }; + }>( + `/org/${clientRow.orgId}/approvals?approvalState=pending&clientId=${clientRow.id}` + ); + const approval = approvalsRes.data.data.approvals[0]; if (!approval) { @@ -202,9 +209,12 @@ export default function UserDevicesTable({ userClients }: ClientTableProps) { return; } - await api.put(`/org/${clientRow.orgId}/approvals/${approval.approvalId}`, { - decision: "approved" - }); + await api.put( + `/org/${clientRow.orgId}/approvals/${approval.approvalId}`, + { + decision: "approved" + } + ); toast({ title: t("accessApprovalUpdated"), @@ -230,9 +240,13 @@ export default function UserDevicesTable({ userClients }: ClientTableProps) { try { // Fetch approvalId for this client using clientId query parameter const approvalsRes = await api.get<{ - data: { approvals: Array<{ approvalId: number; clientId: number }> }; - }>(`/org/${clientRow.orgId}/approvals?approvalState=pending&clientId=${clientRow.id}`); - + data: { + approvals: Array<{ approvalId: number; clientId: number }>; + }; + }>( + `/org/${clientRow.orgId}/approvals?approvalState=pending&clientId=${clientRow.id}` + ); + const approval = approvalsRes.data.data.approvals[0]; if (!approval) { @@ -244,9 +258,12 @@ export default function UserDevicesTable({ userClients }: ClientTableProps) { return; } - await api.put(`/org/${clientRow.orgId}/approvals/${approval.approvalId}`, { - decision: "denied" - }); + await api.put( + `/org/${clientRow.orgId}/approvals/${approval.approvalId}`, + { + decision: "denied" + } + ); toast({ title: t("accessApprovalUpdated"), @@ -398,7 +415,7 @@ export default function UserDevicesTable({ userClients }: ClientTableProps) { }, { accessorKey: "online", - friendlyName: t("connectivity"), + friendlyName: t("online"), header: ({ column }) => { return ( + + + + {filter.label} + + + {filter.options.map( + (option) => { + const isChecked = + selectedValues.includes( + option.value + ); + return ( + { + // handleFilterChange( + // filter.id, + // option.value, + // checked + // ) + }} + onSelect={(e) => + e.preventDefault() + } + > + {option.label} + + ); + } + )} + + + ); + })} +
+ )} +
+
+ {onRefresh && ( +
+ +
+ )} + {onAdd && addButtonText && ( +
+ +
+ )} +
+ + +
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + const columnId = header.column.id; + const accessorKey = ( + header.column.columnDef as any + ).accessorKey as string | undefined; + const stickyClasses = + getStickyClasses( + columnId, + accessorKey + ); + const isRightSticky = + isStickyColumn( + columnId, + accessorKey, + "right" + ); + const hasHideableColumns = + enableColumnVisibility && + table + .getAllColumns() + .some((col) => + col.getCanHide() + ); + + return ( + + {header.isPlaceholder ? null : isRightSticky && + hasHideableColumns ? ( +
+ + + + + + + {t( + "toggleColumns" + ) || + "Toggle columns"} + + + {table + .getAllColumns() + .filter( + ( + column + ) => + column.getCanHide() + ) + .map( + ( + column + ) => { + const columnDef = + column.columnDef as any; + const friendlyName = + columnDef.friendlyName; + const displayName = + friendlyName || + (typeof columnDef.header === + "string" + ? columnDef.header + : column.id); + return ( + + column.toggleVisibility( + !!value + ) + } + onSelect={( + e + ) => + e.preventDefault() + } + > + { + displayName + } + + ); + } + )} + + +
+ {flexRender( + header + .column + .columnDef + .header, + header.getContext() + )} +
+
+ ) : ( + flexRender( + header.column + .columnDef + .header, + header.getContext() + ) + )} +
+ ); + })} +
+ ))} +
+ + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => ( + + {row + .getVisibleCells() + .map((cell) => { + const columnId = + cell.column.id; + const accessorKey = ( + cell.column + .columnDef as any + ).accessorKey as + | string + | undefined; + const stickyClasses = + getStickyClasses( + columnId, + accessorKey + ); + const isRightSticky = + isStickyColumn( + columnId, + accessorKey, + "right" + ); + return ( + + {flexRender( + cell.column + .columnDef + .cell, + cell.getContext() + )} + + ); + })} + + )) + ) : ( + + + No results found. + + + )} + +
+
+
+ {rowCount > 0 && ( + + onPaginationChange({ + ...pagination, + pageSize + }) + } + onPageChange={(pageIndex) => { + onPaginationChange({ + ...pagination, + pageIndex + }); + }} + isServerPagination + pageSize={pagination.pageSize} + pageIndex={pagination.pageIndex} + /> + )} +
+
+ +
+ ); +} From 066305b09558aee3ed7bace863976afe5324deae Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Sat, 31 Jan 2026 00:45:14 +0100 Subject: [PATCH 08/81] =?UTF-8?q?=E2=9C=A8=20toggle=20column=20sorting=20&?= =?UTF-8?q?=20pagination?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/routers/site/listSites.ts | 73 ++++++++--------- src/components/SitesTable.tsx | 129 ++++++++++--------------------- src/hooks/useSortColumn.ts | 56 ++++++++++++++ src/lib/types/sort.ts | 1 + 4 files changed, 135 insertions(+), 124 deletions(-) create mode 100644 src/hooks/useSortColumn.ts create mode 100644 src/lib/types/sort.ts diff --git a/server/routers/site/listSites.ts b/server/routers/site/listSites.ts index e77f6333..9c25897e 100644 --- a/server/routers/site/listSites.ts +++ b/server/routers/site/listSites.ts @@ -4,7 +4,17 @@ import { remoteExitNodes } from "@server/db"; import logger from "@server/logger"; import HttpCode from "@server/types/HttpCode"; import response from "@server/lib/response"; -import { and, count, eq, ilike, inArray, or, sql } from "drizzle-orm"; +import { + and, + asc, + count, + desc, + eq, + ilike, + inArray, + or, + sql +} from "drizzle-orm"; import { NextFunction, Request, Response } from "express"; import createHttpError from "http-errors"; import { z } from "zod"; @@ -88,28 +98,15 @@ const listSitesSchema = z.object({ .optional() .catch(1) .default(1), - query: z.string().optional() + query: z.string().optional(), + sort_by: z + .enum(["megabytesIn", "megabytesOut"]) + .optional() + .catch(undefined), + order: z.enum(["asc", "desc"]).optional().default("asc").catch("asc") }); -function querySites( - orgId: string, - accessibleSiteIds: number[], - query: string = "" -) { - let conditions = and( - inArray(sites.siteId, accessibleSiteIds), - eq(sites.orgId, orgId) - ); - - if (query) { - conditions = and( - conditions, - or( - ilike(sites.name, "%" + query + "%"), - ilike(sites.niceId, "%" + query + "%") - ) - ); - } +function querySitesBase() { return db .select({ siteId: sites.siteId, @@ -136,11 +133,10 @@ function querySites( .leftJoin( remoteExitNodes, eq(remoteExitNodes.exitNodeId, sites.exitNodeId) - ) - .where(conditions); + ); } -type SiteWithUpdateAvailable = Awaited>[0] & { +type SiteWithUpdateAvailable = Awaited>[0] & { newtUpdateAvailable?: boolean; }; @@ -176,7 +172,7 @@ export async function listSites( ) ); } - const { pageSize, page, query } = parsedQuery.data; + const { pageSize, page, query, sort_by, order } = parsedQuery.data; const parsedParams = listSitesParamsSchema.safeParse(req.params); if (!parsedParams.success) { @@ -220,7 +216,7 @@ export async function listSites( } const accessibleSiteIds = accessibleSites.map((site) => site.siteId); - const baseQuery = querySites(orgId, accessibleSiteIds, query); + const baseQuery = querySitesBase(); let conditions = and( inArray(sites.siteId, accessibleSiteIds), @@ -241,23 +237,30 @@ export async function listSites( .from(sites) .where(conditions); - const sitesList = await baseQuery + const siteListQuery = baseQuery + .where(conditions) .limit(pageSize) .offset(pageSize * (page - 1)); + + if (sort_by) { + siteListQuery.orderBy( + order === "asc" ? asc(sites[sort_by]) : desc(sites[sort_by]) + ); + } const totalCountResult = await countQuery; const totalCount = totalCountResult[0].count; // Get latest version asynchronously without blocking the response const latestNewtVersionPromise = getLatestNewtVersion(); - const sitesWithUpdates: SiteWithUpdateAvailable[] = sitesList.map( - (site) => { - const siteWithUpdate: SiteWithUpdateAvailable = { ...site }; - // Initially set to false, will be updated if version check succeeds - siteWithUpdate.newtUpdateAvailable = false; - return siteWithUpdate; - } - ); + const sitesWithUpdates: SiteWithUpdateAvailable[] = ( + await siteListQuery + ).map((site) => { + const siteWithUpdate: SiteWithUpdateAvailable = { ...site }; + // Initially set to false, will be updated if version check succeeds + siteWithUpdate.newtUpdateAvailable = false; + return siteWithUpdate; + }); // Try to get the latest version, but don't block if it fails try { diff --git a/src/components/SitesTable.tsx b/src/components/SitesTable.tsx index 68a7fc37..f99da889 100644 --- a/src/components/SitesTable.tsx +++ b/src/components/SitesTable.tsx @@ -12,15 +12,18 @@ import { } from "@app/components/ui/dropdown-menu"; import { InfoPopup } from "@app/components/ui/info-popup"; import { useEnvContext } from "@app/hooks/useEnvContext"; +import { useSortColumn } from "@app/hooks/useSortColumn"; import { toast } from "@app/hooks/useToast"; import { createApiClient, formatAxiosError } from "@app/lib/api"; import { parseDataSize } from "@app/lib/dataSize"; import { build } from "@server/build"; -import { Column, type PaginationState } from "@tanstack/react-table"; +import { type PaginationState } from "@tanstack/react-table"; import { + ArrowDown01Icon, ArrowRight, - ArrowUpDown, + ArrowUp10Icon, ArrowUpRight, + ChevronsUpDownIcon, MoreHorizontal } from "lucide-react"; import { useTranslations } from "next-intl"; @@ -71,6 +74,8 @@ export default function SitesTable({ const [selectedSite, setSelectedSite] = useState(null); const [isRefreshing, startTransition] = useTransition(); + const [getSortDirection, toggleSorting] = useSortColumn(); + const api = createApiClient(useEnvContext()); const t = useTranslations(); @@ -102,22 +107,15 @@ export default function SitesTable({ }); }; + const dataInOrder = getSortDirection("megabytesIn"); + const dataOutOrder = getSortDirection("megabytesOut"); + const columns: ExtendedColumnDef[] = [ { accessorKey: "name", enableHiding: false, - header: ({ column }) => { - return ( - - ); + header: () => { + return {t("name")}; } }, { @@ -125,18 +123,8 @@ export default function SitesTable({ accessorKey: "nice", friendlyName: t("identifier"), enableHiding: true, - header: ({ column }) => { - return ( - - ); + header: () => { + return {t("identifier")}; }, cell: ({ row }) => { return {row.original.nice || "-"}; @@ -145,18 +133,8 @@ export default function SitesTable({ { accessorKey: "online", friendlyName: t("online"), - header: ({ column }) => { - return ( - - ); + header: () => { + return {t("online")}; }, cell: ({ row }) => { const originalRow = row.original; @@ -187,16 +165,20 @@ export default function SitesTable({ { accessorKey: "mbIn", friendlyName: t("dataIn"), - header: ({ column }) => { + header: () => { + const Icon = + dataInOrder === "asc" + ? ArrowDown01Icon + : dataInOrder === "desc" + ? ArrowUp10Icon + : ChevronsUpDownIcon; return ( ); }, @@ -207,16 +189,20 @@ export default function SitesTable({ { accessorKey: "mbOut", friendlyName: t("dataOut"), - header: ({ column }) => { + header: () => { + const Icon = + dataOutOrder === "asc" + ? ArrowDown01Icon + : dataOutOrder === "desc" + ? ArrowUp10Icon + : ChevronsUpDownIcon; return ( ); }, @@ -227,18 +213,8 @@ export default function SitesTable({ { accessorKey: "type", friendlyName: t("type"), - header: ({ column }) => { - return ( - - ); + header: () => { + return {t("type")}; }, cell: ({ row }) => { const originalRow = row.original; @@ -283,18 +259,8 @@ export default function SitesTable({ { accessorKey: "exitNode", friendlyName: t("exitNode"), - header: ({ column }) => { - return ( - - ); + header: () => { + return {t("exitNode")}; }, cell: ({ row }) => { const originalRow = row.original; @@ -347,18 +313,8 @@ export default function SitesTable({ }, { accessorKey: "address", - header: ({ column }: { column: Column }) => { - return ( - - ); + header: () => { + return {t("address")}; }, cell: ({ row }: { row: any }) => { const originalRow = row.original; @@ -435,11 +391,6 @@ export default function SitesTable({ startTransition(() => router.push(`${pathname}?${sp.toString()}`)); }, 300); - console.log({ - pagination, - rowCount - }); - return ( <> {selectedSite && ( diff --git a/src/hooks/useSortColumn.ts b/src/hooks/useSortColumn.ts new file mode 100644 index 00000000..95fb673e --- /dev/null +++ b/src/hooks/useSortColumn.ts @@ -0,0 +1,56 @@ +import type { SortOrder } from "@app/lib/types/sort"; +import { useSearchParams, useRouter, usePathname } from "next/navigation"; +import { startTransition } from "react"; + +export function useSortColumn() { + const router = useRouter(); + const pathname = usePathname(); + const searchParams = useSearchParams(); + + const toggleSorting = (column: string) => { + const sp = new URLSearchParams(searchParams); + + let nextDirection: SortOrder = "indeterminate"; + + if (sp.get("sort_by") === column) { + nextDirection = (sp.get("order") as SortOrder) ?? "indeterminate"; + } + + switch (nextDirection) { + case "indeterminate": { + nextDirection = "asc"; + break; + } + case "asc": { + nextDirection = "desc"; + break; + } + default: { + nextDirection = "indeterminate"; + break; + } + } + + sp.delete("sort_by"); + sp.delete("order"); + + if (nextDirection !== "indeterminate") { + sp.set("sort_by", column); + sp.set("order", nextDirection); + } + + startTransition(() => router.push(`${pathname}?${sp.toString()}`)); + }; + + function getSortDirection(column: string) { + let currentDirection: SortOrder = "indeterminate"; + + if (searchParams.get("sort_by") === column) { + currentDirection = + (searchParams.get("order") as SortOrder) ?? "indeterminate"; + } + return currentDirection; + } + + return [getSortDirection, toggleSorting] as const; +} diff --git a/src/lib/types/sort.ts b/src/lib/types/sort.ts new file mode 100644 index 00000000..69161f5a --- /dev/null +++ b/src/lib/types/sort.ts @@ -0,0 +1 @@ +export type SortOrder = "asc" | "desc" | "indeterminate"; From cda6b67befb30b2bb0b501af9814359333c14279 Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Sat, 31 Jan 2026 03:02:39 +0100 Subject: [PATCH 09/81] =?UTF-8?q?=E2=9C=A8=20search,=20filter=20&=20pagina?= =?UTF-8?q?te=20sites=20table?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- messages/en-US.json | 3 +- server/routers/site/listSites.ts | 14 ++- src/components/ColumnFilter.tsx | 16 ++-- src/components/ColumnFilterButton.tsx | 126 ++++++++++++++++++++++++++ src/components/OrgSelector.tsx | 2 +- src/components/SitesTable.tsx | 99 ++++++++++++++------ 6 files changed, 223 insertions(+), 37 deletions(-) create mode 100644 src/components/ColumnFilterButton.tsx diff --git a/messages/en-US.json b/messages/en-US.json index f2affe11..8fffe02d 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -1164,7 +1164,8 @@ "actionViewLogs": "View Logs", "noneSelected": "None selected", "orgNotFound2": "No organizations found.", - "searchProgress": "Search...", + "searchPlaceholder": "Search...", + "emptySearchOptions": "No options found", "create": "Create", "orgs": "Organizations", "loginError": "An unexpected error occurred. Please try again.", diff --git a/server/routers/site/listSites.ts b/server/routers/site/listSites.ts index 9c25897e..1cc54fab 100644 --- a/server/routers/site/listSites.ts +++ b/server/routers/site/listSites.ts @@ -103,7 +103,12 @@ const listSitesSchema = z.object({ .enum(["megabytesIn", "megabytesOut"]) .optional() .catch(undefined), - order: z.enum(["asc", "desc"]).optional().default("asc").catch("asc") + order: z.enum(["asc", "desc"]).optional().default("asc").catch("asc"), + online: z + .enum(["true", "false"]) + .transform((v) => v === "true") + .optional() + .catch(undefined) }); function querySitesBase() { @@ -172,7 +177,6 @@ export async function listSites( ) ); } - const { pageSize, page, query, sort_by, order } = parsedQuery.data; const parsedParams = listSitesParamsSchema.safeParse(req.params); if (!parsedParams.success) { @@ -215,6 +219,9 @@ export async function listSites( .where(eq(sites.orgId, orgId)); } + const { pageSize, page, query, sort_by, order, online } = + parsedQuery.data; + const accessibleSiteIds = accessibleSites.map((site) => site.siteId); const baseQuery = querySitesBase(); @@ -231,6 +238,9 @@ export async function listSites( ) ); } + if (typeof online !== "undefined") { + conditions = and(conditions, eq(sites.online, online)); + } const countQuery = db .select({ count: count() }) diff --git a/src/components/ColumnFilter.tsx b/src/components/ColumnFilter.tsx index a856984e..3e7b585b 100644 --- a/src/components/ColumnFilter.tsx +++ b/src/components/ColumnFilter.tsx @@ -15,6 +15,7 @@ import { } from "@app/components/ui/command"; import { CheckIcon, ChevronDownIcon, Filter } from "lucide-react"; import { cn } from "@app/lib/cn"; +import { Badge } from "./ui/badge"; interface FilterOption { value: string; @@ -61,16 +62,19 @@ export function ColumnFilter({ >
- - {selectedOption - ? selectedOption.label - : placeholder} - + + {selectedOption && ( + + {selectedOption + ? selectedOption.label + : placeholder} + + )}
- + diff --git a/src/components/ColumnFilterButton.tsx b/src/components/ColumnFilterButton.tsx new file mode 100644 index 00000000..7d17066c --- /dev/null +++ b/src/components/ColumnFilterButton.tsx @@ -0,0 +1,126 @@ +import { useState } from "react"; +import { Button } from "@app/components/ui/button"; +import { + Popover, + PopoverContent, + PopoverTrigger +} from "@app/components/ui/popover"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList +} from "@app/components/ui/command"; +import { CheckIcon, ChevronDownIcon, Funnel } from "lucide-react"; +import { cn } from "@app/lib/cn"; +import { Badge } from "./ui/badge"; + +interface FilterOption { + value: string; + label: string; +} + +interface ColumnFilterButtonProps { + options: FilterOption[]; + selectedValue?: string; + onValueChange: (value: string | undefined) => void; + placeholder?: string; + searchPlaceholder?: string; + emptyMessage?: string; + className?: string; + label: string; +} + +export function ColumnFilterButton({ + options, + selectedValue, + onValueChange, + placeholder, + searchPlaceholder = "Search...", + emptyMessage = "No options found", + className, + label +}: ColumnFilterButtonProps) { + const [open, setOpen] = useState(false); + + const selectedOption = options.find( + (option) => option.value === selectedValue + ); + + return ( + + + + + + + + + {emptyMessage} + + {/* Clear filter option */} + {selectedValue && ( + { + onValueChange(undefined); + setOpen(false); + }} + className="text-muted-foreground" + > + Clear filter + + )} + {options.map((option) => ( + { + onValueChange( + selectedValue === option.value + ? undefined + : option.value + ); + setOpen(false); + }} + > + + {option.label} + + ))} + + + + + + ); +} diff --git a/src/components/OrgSelector.tsx b/src/components/OrgSelector.tsx index b2939a90..e139e43a 100644 --- a/src/components/OrgSelector.tsx +++ b/src/components/OrgSelector.tsx @@ -83,7 +83,7 @@ export function OrgSelector({ diff --git a/src/components/SitesTable.tsx b/src/components/SitesTable.tsx index f99da889..5076149f 100644 --- a/src/components/SitesTable.tsx +++ b/src/components/SitesTable.tsx @@ -24,6 +24,7 @@ import { ArrowUp10Icon, ArrowUpRight, ChevronsUpDownIcon, + Funnel, MoreHorizontal } from "lucide-react"; import { useTranslations } from "next-intl"; @@ -35,6 +36,9 @@ import { ManualDataTable, type ExtendedColumnDef } from "./ui/manual-data-table"; +import { ColumnFilter } from "./ColumnFilter"; +import { ColumnFilterButton } from "./ColumnFilterButton"; +import z from "zod"; export type SiteRow = { id: number; @@ -79,33 +83,57 @@ export default function SitesTable({ const api = createApiClient(useEnvContext()); const t = useTranslations(); - const refreshData = async () => { - try { - router.refresh(); - } catch (error) { - toast({ - title: t("error"), - description: t("refreshError"), - variant: "destructive" - }); - } - }; + const booleanSearchFilterSchema = z + .enum(["true", "false"]) + .optional() + .catch(undefined); - const deleteSite = (siteId: number) => { - api.delete(`/site/${siteId}`) - .catch((e) => { - console.error(t("siteErrorDelete"), e); - toast({ - variant: "destructive", - title: t("siteErrorDelete"), - description: formatAxiosError(e, t("siteErrorDelete")) - }); - }) - .then(() => { + function handleFilterChange( + column: string, + value: string | undefined | null + ) { + const sp = new URLSearchParams(searchParams); + sp.delete(column); + sp.delete("page"); + + if (value) { + sp.set(column, value); + } + startTransition(() => router.push(`${pathname}?${sp.toString()}`)); + } + + function refreshData() { + startTransition(async () => { + try { router.refresh(); - setIsDeleteModalOpen(false); - }); - }; + } catch (error) { + toast({ + title: t("error"), + description: t("refreshError"), + variant: "destructive" + }); + } + }); + } + + function deleteSite(siteId: number) { + startTransition(async () => { + await api + .delete(`/site/${siteId}`) + .catch((e) => { + console.error(t("siteErrorDelete"), e); + toast({ + variant: "destructive", + title: t("siteErrorDelete"), + description: formatAxiosError(e, t("siteErrorDelete")) + }); + }) + .then(() => { + router.refresh(); + setIsDeleteModalOpen(false); + }); + }); + } const dataInOrder = getSortDirection("megabytesIn"); const dataOutOrder = getSortDirection("megabytesOut"); @@ -134,7 +162,24 @@ export default function SitesTable({ accessorKey: "online", friendlyName: t("online"), header: () => { - return {t("online")}; + return ( + + handleFilterChange("online", value) + } + searchPlaceholder={t("searchPlaceholder")} + emptyMessage={t("emptySearchOptions")} + label={t("online")} + className="p-3" + /> + ); }, cell: ({ row }) => { const originalRow = row.original; @@ -426,7 +471,7 @@ export default function SitesTable({ searchQuery={searchParams.get("query")?.toString()} onSearch={handleSearchChange} addButtonText={t("siteAdd")} - onRefresh={() => startTransition(refreshData)} + onRefresh={refreshData} isRefreshing={isRefreshing} rowCount={rowCount} columnVisibility={{ From bd8d0e3392d95f55932740a1f81882ae3de10f1b Mon Sep 17 00:00:00 2001 From: Lokowitz Date: Mon, 2 Feb 2026 18:48:35 +0000 Subject: [PATCH 10/81] update packages --- esbuild.mjs | 2 +- package-lock.json | 2510 +++++++++++++++----------------------- package.json | 54 +- tsconfig.enterprise.json | 2 +- tsconfig.oss.json | 2 +- tsconfig.saas.json | 2 +- 6 files changed, 1012 insertions(+), 1560 deletions(-) diff --git a/esbuild.mjs b/esbuild.mjs index 0157c34a..f20a1988 100644 --- a/esbuild.mjs +++ b/esbuild.mjs @@ -275,7 +275,7 @@ esbuild }) ], sourcemap: "inline", - target: "node22" + target: "node24" }) .then((result) => { // Check if there were any errors in the build result diff --git a/package-lock.json b/package-lock.json index 4a01c8c5..1353ad7f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "SEE LICENSE IN LICENSE AND README.md", "dependencies": { "@asteasolutions/zod-to-openapi": "8.4.0", - "@aws-sdk/client-s3": "3.971.0", + "@aws-sdk/client-s3": "3.980.0", "@faker-js/faker": "10.2.0", "@headlessui/react": "2.2.9", "@hookform/resolvers": "5.2.2", @@ -36,16 +36,16 @@ "@radix-ui/react-tabs": "1.1.13", "@radix-ui/react-toast": "1.2.15", "@radix-ui/react-tooltip": "1.2.8", - "@react-email/components": "1.0.2", - "@react-email/render": "2.0.0", - "@react-email/tailwind": "2.0.2", + "@react-email/components": "1.0.6", + "@react-email/render": "2.0.4", + "@react-email/tailwind": "2.0.3", "@simplewebauthn/browser": "13.2.2", "@simplewebauthn/server": "13.2.2", "@tailwindcss/forms": "0.5.11", - "@tanstack/react-query": "5.90.12", + "@tanstack/react-query": "5.90.20", "@tanstack/react-table": "8.21.3", "arctic": "3.7.0", - "axios": "1.13.2", + "axios": "1.13.4", "better-sqlite3": "11.9.1", "canvas-confetti": "1.9.4", "class-variance-authority": "0.7.1", @@ -54,13 +54,13 @@ "cookie": "1.1.1", "cookie-parser": "1.4.7", "cookies": "0.9.1", - "cors": "2.8.5", + "cors": "2.8.6", "crypto-js": "4.2.0", "d3": "7.9.0", "date-fns": "4.1.0", "drizzle-orm": "0.45.1", "eslint": "9.39.2", - "eslint-config-next": "16.1.0", + "eslint-config-next": "16.1.6", "express": "5.2.1", "express-rate-limit": "8.2.1", "glob": "13.0.0", @@ -72,34 +72,34 @@ "jmespath": "0.16.0", "js-yaml": "4.1.1", "jsonwebtoken": "9.0.3", - "lucide-react": "0.562.0", - "maxmind": "5.0.1", + "lucide-react": "0.563.0", + "maxmind": "5.0.5", "moment": "2.30.1", "next": "15.5.9", - "next-intl": "4.7.0", + "next-intl": "4.8.2", "next-themes": "0.4.6", "nextjs-toploader": "3.9.17", "node-cache": "5.1.2", "node-fetch": "3.3.2", - "nodemailer": "7.0.11", - "npm": "11.7.0", + "nodemailer": "7.0.13", + "npm": "11.8.0", "nprogress": "0.2.0", "oslo": "1.2.1", - "pg": "8.17.1", - "posthog-node": "5.23.0", + "pg": "8.18.0", + "posthog-node": "5.24.7", "qrcode.react": "4.2.0", - "react": "19.2.3", + "react": "19.2.4", "react-day-picker": "9.13.0", - "react-dom": "19.2.3", + "react-dom": "19.2.4", "react-easy-sort": "1.8.0", "react-hook-form": "7.71.1", "react-icons": "5.5.0", "rebuild": "0.1.2", "recharts": "2.15.4", "reodotdev": "1.0.0", - "resend": "6.8.0", + "resend": "6.9.1", "semver": "7.7.3", - "stripe": "20.2.0", + "stripe": "20.3.0", "swagger-ui-express": "5.0.1", "tailwind-merge": "3.4.0", "topojson-client": "3.1.0", @@ -112,14 +112,14 @@ "ws": "8.19.0", "yaml": "2.8.2", "yargs": "18.0.0", - "zod": "4.3.5", + "zod": "4.3.6", "zod-validation-error": "5.0.0" }, "devDependencies": { - "@dotenvx/dotenvx": "1.51.2", + "@dotenvx/dotenvx": "1.52.0", "@esbuild-plugins/tsconfig-paths": "0.1.2", "@tailwindcss/postcss": "4.1.18", - "@tanstack/react-query-devtools": "5.91.1", + "@tanstack/react-query-devtools": "5.91.3", "@types/better-sqlite3": "7.6.13", "@types/cookie-parser": "1.4.10", "@types/cors": "2.8.19", @@ -130,11 +130,11 @@ "@types/jmespath": "0.15.2", "@types/js-yaml": "4.0.9", "@types/jsonwebtoken": "9.0.10", - "@types/node": "24.10.2", - "@types/nodemailer": "7.0.4", + "@types/node": "25.2.0", + "@types/nodemailer": "7.0.9", "@types/nprogress": "0.2.3", "@types/pg": "8.16.0", - "@types/react": "19.2.7", + "@types/react": "19.2.10", "@types/react-dom": "19.2.3", "@types/semver": "7.7.1", "@types/swagger-ui-express": "4.1.8", @@ -146,13 +146,13 @@ "esbuild": "0.27.2", "esbuild-node-externals": "1.20.1", "postcss": "8.5.6", - "prettier": "3.8.0", + "prettier": "3.8.1", "react-email": "5.2.5", "tailwindcss": "4.1.18", "tsc-alias": "1.8.16", "tsx": "4.21.0", "typescript": "5.9.3", - "typescript-eslint": "8.53.1" + "typescript-eslint": "8.54.0" } }, "node_modules/@alloc/quick-lru": { @@ -396,34 +396,34 @@ } }, "node_modules/@aws-sdk/client-s3": { - "version": "3.971.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.971.0.tgz", - "integrity": "sha512-BBUne390fKa4C4QvZlUZ5gKcu+Uyid4IyQ20N4jl0vS7SK2xpfXlJcgKqPW5ts6kx6hWTQBk6sH5Lf12RvuJxg==", + "version": "3.980.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.980.0.tgz", + "integrity": "sha512-ch8QqKehyn1WOYbd8LyDbWjv84Z9OEj9qUxz8q3IOCU3ftAVkVR0wAuN96a1xCHnpOJcQZo3rOB08RlyKdkGxQ==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.970.0", - "@aws-sdk/credential-provider-node": "3.971.0", - "@aws-sdk/middleware-bucket-endpoint": "3.969.0", - "@aws-sdk/middleware-expect-continue": "3.969.0", - "@aws-sdk/middleware-flexible-checksums": "3.971.0", - "@aws-sdk/middleware-host-header": "3.969.0", - "@aws-sdk/middleware-location-constraint": "3.969.0", - "@aws-sdk/middleware-logger": "3.969.0", - "@aws-sdk/middleware-recursion-detection": "3.969.0", - "@aws-sdk/middleware-sdk-s3": "3.970.0", - "@aws-sdk/middleware-ssec": "3.971.0", - "@aws-sdk/middleware-user-agent": "3.970.0", - "@aws-sdk/region-config-resolver": "3.969.0", - "@aws-sdk/signature-v4-multi-region": "3.970.0", - "@aws-sdk/types": "3.969.0", - "@aws-sdk/util-endpoints": "3.970.0", - "@aws-sdk/util-user-agent-browser": "3.969.0", - "@aws-sdk/util-user-agent-node": "3.971.0", + "@aws-sdk/core": "^3.973.5", + "@aws-sdk/credential-provider-node": "^3.972.4", + "@aws-sdk/middleware-bucket-endpoint": "^3.972.3", + "@aws-sdk/middleware-expect-continue": "^3.972.3", + "@aws-sdk/middleware-flexible-checksums": "^3.972.3", + "@aws-sdk/middleware-host-header": "^3.972.3", + "@aws-sdk/middleware-location-constraint": "^3.972.3", + "@aws-sdk/middleware-logger": "^3.972.3", + "@aws-sdk/middleware-recursion-detection": "^3.972.3", + "@aws-sdk/middleware-sdk-s3": "^3.972.5", + "@aws-sdk/middleware-ssec": "^3.972.3", + "@aws-sdk/middleware-user-agent": "^3.972.5", + "@aws-sdk/region-config-resolver": "^3.972.3", + "@aws-sdk/signature-v4-multi-region": "3.980.0", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-endpoints": "3.980.0", + "@aws-sdk/util-user-agent-browser": "^3.972.3", + "@aws-sdk/util-user-agent-node": "^3.972.3", "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.20.6", + "@smithy/core": "^3.22.0", "@smithy/eventstream-serde-browser": "^4.2.8", "@smithy/eventstream-serde-config-resolver": "^4.3.8", "@smithy/eventstream-serde-node": "^4.2.8", @@ -434,21 +434,21 @@ "@smithy/invalid-dependency": "^4.2.8", "@smithy/md5-js": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.7", - "@smithy/middleware-retry": "^4.4.23", + "@smithy/middleware-endpoint": "^4.4.12", + "@smithy/middleware-retry": "^4.4.29", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.8", "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.8", + "@smithy/smithy-client": "^4.11.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.22", - "@smithy/util-defaults-mode-node": "^4.2.25", + "@smithy/util-defaults-mode-browser": "^4.3.28", + "@smithy/util-defaults-mode-node": "^4.2.31", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", @@ -461,647 +461,83 @@ "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/client-sso": { - "version": "3.971.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.971.0.tgz", - "integrity": "sha512-Xx+w6DQqJxDdymYyIxyKJnRzPvVJ4e/Aw0czO7aC9L/iraaV7AG8QtRe93OGW6aoHSh72CIiinnpJJfLsQqP4g==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.970.0", - "@aws-sdk/middleware-host-header": "3.969.0", - "@aws-sdk/middleware-logger": "3.969.0", - "@aws-sdk/middleware-recursion-detection": "3.969.0", - "@aws-sdk/middleware-user-agent": "3.970.0", - "@aws-sdk/region-config-resolver": "3.969.0", - "@aws-sdk/types": "3.969.0", - "@aws-sdk/util-endpoints": "3.970.0", - "@aws-sdk/util-user-agent-browser": "3.969.0", - "@aws-sdk/util-user-agent-node": "3.971.0", - "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.20.6", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/hash-node": "^4.2.8", - "@smithy/invalid-dependency": "^4.2.8", - "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.7", - "@smithy/middleware-retry": "^4.4.23", - "@smithy/middleware-serde": "^4.2.9", - "@smithy/middleware-stack": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.8", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.22", - "@smithy/util-defaults-mode-node": "^4.2.25", - "@smithy/util-endpoints": "^3.2.8", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-retry": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/core": { - "version": "3.970.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.970.0.tgz", - "integrity": "sha512-klpzObldOq8HXzDjDlY6K8rMhYZU6mXRz6P9F9N+tWnjoYFfeBMra8wYApydElTUYQKP1O7RLHwH1OKFfKcqIA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.969.0", - "@aws-sdk/xml-builder": "3.969.0", - "@smithy/core": "^3.20.6", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/signature-v4": "^5.3.8", - "@smithy/smithy-client": "^4.10.8", - "@smithy/types": "^4.12.0", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-env": { - "version": "3.970.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.970.0.tgz", - "integrity": "sha512-rtVzXzEtAfZBfh+lq3DAvRar4c3jyptweOAJR2DweyXx71QSMY+O879hjpMwES7jl07a3O1zlnFIDo4KP/96kQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.970.0", - "@aws-sdk/types": "3.969.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.970.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.970.0.tgz", - "integrity": "sha512-CjDbWL7JxjLc9ZxQilMusWSw05yRvUJKRpz59IxDpWUnSMHC9JMMUUkOy5Izk8UAtzi6gupRWArp4NG4labt9Q==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.970.0", - "@aws-sdk/types": "3.969.0", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.8", - "@smithy/types": "^4.12.0", - "@smithy/util-stream": "^4.5.10", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.971.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.971.0.tgz", - "integrity": "sha512-c0TGJG4xyfTZz3SInXfGU8i5iOFRrLmy4Bo7lMyH+IpngohYMYGYl61omXqf2zdwMbDv+YJ9AviQTcCaEUKi8w==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.970.0", - "@aws-sdk/credential-provider-env": "3.970.0", - "@aws-sdk/credential-provider-http": "3.970.0", - "@aws-sdk/credential-provider-login": "3.971.0", - "@aws-sdk/credential-provider-process": "3.970.0", - "@aws-sdk/credential-provider-sso": "3.971.0", - "@aws-sdk/credential-provider-web-identity": "3.971.0", - "@aws-sdk/nested-clients": "3.971.0", - "@aws-sdk/types": "3.969.0", - "@smithy/credential-provider-imds": "^4.2.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-login": { - "version": "3.971.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.971.0.tgz", - "integrity": "sha512-yhbzmDOsk0RXD3rTPhZra4AWVnVAC4nFWbTp+sUty1hrOPurUmhuz8bjpLqYTHGnlMbJp+UqkQONhS2+2LzW2g==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.970.0", - "@aws-sdk/nested-clients": "3.971.0", - "@aws-sdk/types": "3.969.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.971.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.971.0.tgz", - "integrity": "sha512-epUJBAKivtJqalnEBRsYIULKYV063o/5mXNJshZfyvkAgNIzc27CmmKRXTN4zaNOZg8g/UprFp25BGsi19x3nQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.970.0", - "@aws-sdk/credential-provider-http": "3.970.0", - "@aws-sdk/credential-provider-ini": "3.971.0", - "@aws-sdk/credential-provider-process": "3.970.0", - "@aws-sdk/credential-provider-sso": "3.971.0", - "@aws-sdk/credential-provider-web-identity": "3.971.0", - "@aws-sdk/types": "3.969.0", - "@smithy/credential-provider-imds": "^4.2.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-process": { - "version": "3.970.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.970.0.tgz", - "integrity": "sha512-0XeT8OaT9iMA62DFV9+m6mZfJhrD0WNKf4IvsIpj2Z7XbaYfz3CoDDvNoALf3rPY9NzyMHgDxOspmqdvXP00mw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.970.0", - "@aws-sdk/types": "3.969.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.971.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.971.0.tgz", - "integrity": "sha512-dY0hMQ7dLVPQNJ8GyqXADxa9w5wNfmukgQniLxGVn+dMRx3YLViMp5ZpTSQpFhCWNF0oKQrYAI5cHhUJU1hETw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/client-sso": "3.971.0", - "@aws-sdk/core": "3.970.0", - "@aws-sdk/token-providers": "3.971.0", - "@aws-sdk/types": "3.969.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.971.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.971.0.tgz", - "integrity": "sha512-F1AwfNLr7H52T640LNON/h34YDiMuIqW/ZreGzhRR6vnFGaSPtNSKAKB2ssAMkLM8EVg8MjEAYD3NCUiEo+t/w==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.970.0", - "@aws-sdk/nested-clients": "3.971.0", - "@aws-sdk/types": "3.969.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-host-header": { - "version": "3.969.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.969.0.tgz", - "integrity": "sha512-AWa4rVsAfBR4xqm7pybQ8sUNJYnjyP/bJjfAw34qPuh3M9XrfGbAHG0aiAfQGrBnmS28jlO6Kz69o+c6PRw1dw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.969.0", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-logger": { - "version": "3.969.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.969.0.tgz", - "integrity": "sha512-xwrxfip7Y2iTtCMJ+iifN1E1XMOuhxIHY9DreMCvgdl4r7+48x2S1bCYPWH3eNY85/7CapBWdJ8cerpEl12sQQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.969.0", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.969.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.969.0.tgz", - "integrity": "sha512-2r3PuNquU3CcS1Am4vn/KHFwLi8QFjMdA/R+CRDXT4AFO/0qxevF/YStW3gAKntQIgWgQV8ZdEtKAoJvLI4UWg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.969.0", - "@aws/lambda-invoke-store": "^0.2.2", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.970.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.970.0.tgz", - "integrity": "sha512-v/Y5F1lbFFY7vMeG5yYxuhnn0CAshz6KMxkz1pDyPxejNE9HtA0w8R6OTBh/bVdIm44QpjhbI7qeLdOE/PLzXQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.970.0", - "@aws-sdk/types": "3.969.0", - "@aws-sdk/util-arn-parser": "3.968.0", - "@smithy/core": "^3.20.6", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/signature-v4": "^5.3.8", - "@smithy/smithy-client": "^4.10.8", - "@smithy/types": "^4.12.0", - "@smithy/util-config-provider": "^4.2.0", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-stream": "^4.5.10", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.970.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.970.0.tgz", - "integrity": "sha512-dnSJGGUGSFGEX2NzvjwSefH+hmZQ347AwbLhAsi0cdnISSge+pcGfOFrJt2XfBIypwFe27chQhlfuf/gWdzpZg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.970.0", - "@aws-sdk/types": "3.969.0", - "@aws-sdk/util-endpoints": "3.970.0", - "@smithy/core": "^3.20.6", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/nested-clients": { - "version": "3.971.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.971.0.tgz", - "integrity": "sha512-TWaILL8GyYlhGrxxnmbkazM4QsXatwQgoWUvo251FXmUOsiXDFDVX3hoGIfB3CaJhV2pJPfebHUNJtY6TjZ11g==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.970.0", - "@aws-sdk/middleware-host-header": "3.969.0", - "@aws-sdk/middleware-logger": "3.969.0", - "@aws-sdk/middleware-recursion-detection": "3.969.0", - "@aws-sdk/middleware-user-agent": "3.970.0", - "@aws-sdk/region-config-resolver": "3.969.0", - "@aws-sdk/types": "3.969.0", - "@aws-sdk/util-endpoints": "3.970.0", - "@aws-sdk/util-user-agent-browser": "3.969.0", - "@aws-sdk/util-user-agent-node": "3.971.0", - "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.20.6", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/hash-node": "^4.2.8", - "@smithy/invalid-dependency": "^4.2.8", - "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.7", - "@smithy/middleware-retry": "^4.4.23", - "@smithy/middleware-serde": "^4.2.9", - "@smithy/middleware-stack": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.8", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.22", - "@smithy/util-defaults-mode-node": "^4.2.25", - "@smithy/util-endpoints": "^3.2.8", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-retry": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/region-config-resolver": { - "version": "3.969.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.969.0.tgz", - "integrity": "sha512-scj9OXqKpcjJ4jsFLtqYWz3IaNvNOQTFFvEY8XMJXTv+3qF5I7/x9SJtKzTRJEBF3spjzBUYPtGFbs9sj4fisQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.969.0", - "@smithy/config-resolver": "^4.4.6", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.970.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.970.0.tgz", - "integrity": "sha512-z3syXfuK/x/IsKf/AeYmgc2NT7fcJ+3fHaGO+fkghkV9WEba3fPyOwtTBX4KpFMNb2t50zDGZwbzW1/5ighcUQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/middleware-sdk-s3": "3.970.0", - "@aws-sdk/types": "3.969.0", - "@smithy/protocol-http": "^5.3.8", - "@smithy/signature-v4": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/token-providers": { - "version": "3.971.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.971.0.tgz", - "integrity": "sha512-4hKGWZbmuDdONMJV0HJ+9jwTDb0zLfKxcCLx2GEnBY31Gt9GeyIQ+DZ97Bb++0voawj6pnZToFikXTyrEq2x+w==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.970.0", - "@aws-sdk/nested-clients": "3.971.0", - "@aws-sdk/types": "3.969.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/types": { - "version": "3.969.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.969.0.tgz", - "integrity": "sha512-7IIzM5TdiXn+VtgPdVLjmE6uUBUtnga0f4RiSEI1WW10RPuNvZ9U+pL3SwDiRDAdoGrOF9tSLJOFZmfuwYuVYQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/util-arn-parser": { - "version": "3.968.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.968.0.tgz", - "integrity": "sha512-gqqvYcitIIM2K4lrDX9de9YvOfXBcVdxfT/iLnvHJd4YHvSXlt+gs+AsL4FfPCxG4IG9A+FyulP9Sb1MEA75vw==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/util-endpoints": { - "version": "3.970.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.970.0.tgz", - "integrity": "sha512-TZNZqFcMUtjvhZoZRtpEGQAdULYiy6rcGiXAbLU7e9LSpIYlRqpLa207oMNfgbzlL2PnHko+eVg8rajDiSOYCg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.969.0", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-endpoints": "^3.2.8", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.969.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.969.0.tgz", - "integrity": "sha512-bpJGjuKmFr0rA6UKUCmN8D19HQFMLXMx5hKBXqBlPFdalMhxJSjcxzX9DbQh0Fn6bJtxCguFmRGOBdQqNOt49g==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.969.0", - "@smithy/types": "^4.12.0", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.971.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.971.0.tgz", - "integrity": "sha512-Eygjo9mFzQYjbGY3MYO6CsIhnTwAMd3WmuFalCykqEmj2r5zf0leWrhPaqvA5P68V5JdGfPYgj7vhNOd6CtRBQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/middleware-user-agent": "3.970.0", - "@aws-sdk/types": "3.969.0", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/xml-builder": { - "version": "3.969.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.969.0.tgz", - "integrity": "sha512-BSe4Lx/qdRQQdX8cSSI7Et20vqBspzAjBy8ZmXVoyLkol3y4sXBXzn+BiLtR+oh60ExQn6o2DU4QjdOZbXaKIQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "fast-xml-parser": "5.2.5", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-sesv2": { - "version": "3.946.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sesv2/-/client-sesv2-3.946.0.tgz", - "integrity": "sha512-JYj3BPqgyRXgBjZ3Xvo4Abd+vLxcsHe4gb0TvwiSM/k7e6MRgBZoYwDOnwbNDs/62X1sn7MPHqqB3miuO4nR5g==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.946.0", - "@aws-sdk/credential-provider-node": "3.946.0", - "@aws-sdk/middleware-host-header": "3.936.0", - "@aws-sdk/middleware-logger": "3.936.0", - "@aws-sdk/middleware-recursion-detection": "3.936.0", - "@aws-sdk/middleware-user-agent": "3.946.0", - "@aws-sdk/region-config-resolver": "3.936.0", - "@aws-sdk/signature-v4-multi-region": "3.946.0", - "@aws-sdk/types": "3.936.0", - "@aws-sdk/util-endpoints": "3.936.0", - "@aws-sdk/util-user-agent-browser": "3.936.0", - "@aws-sdk/util-user-agent-node": "3.946.0", - "@smithy/config-resolver": "^4.4.3", - "@smithy/core": "^3.18.7", - "@smithy/fetch-http-handler": "^5.3.6", - "@smithy/hash-node": "^4.2.5", - "@smithy/invalid-dependency": "^4.2.5", - "@smithy/middleware-content-length": "^4.2.5", - "@smithy/middleware-endpoint": "^4.3.14", - "@smithy/middleware-retry": "^4.4.14", - "@smithy/middleware-serde": "^4.2.6", - "@smithy/middleware-stack": "^4.2.5", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/node-http-handler": "^4.4.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/smithy-client": "^4.9.10", - "@smithy/types": "^4.9.0", - "@smithy/url-parser": "^4.2.5", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.13", - "@smithy/util-defaults-mode-node": "^4.2.16", - "@smithy/util-endpoints": "^3.2.5", - "@smithy/util-middleware": "^4.2.5", - "@smithy/util-retry": "^4.2.5", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/@aws-sdk/client-sso": { - "version": "3.946.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.946.0.tgz", - "integrity": "sha512-kGAs5iIVyUz4p6TX3pzG5q3cNxXnVpC4pwRC6DCSaSv9ozyPjc2d74FsK4fZ+J+ejtvCdJk72uiuQtWJc86Wuw==", - "dev": true, + "version": "3.980.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.980.0.tgz", + "integrity": "sha512-AhNXQaJ46C1I+lQ+6Kj+L24il5K9lqqIanJd8lMszPmP7bLnmX0wTKK0dxywcvrLdij3zhWttjAKEBNgLtS8/A==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.946.0", - "@aws-sdk/middleware-host-header": "3.936.0", - "@aws-sdk/middleware-logger": "3.936.0", - "@aws-sdk/middleware-recursion-detection": "3.936.0", - "@aws-sdk/middleware-user-agent": "3.946.0", - "@aws-sdk/region-config-resolver": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@aws-sdk/util-endpoints": "3.936.0", - "@aws-sdk/util-user-agent-browser": "3.936.0", - "@aws-sdk/util-user-agent-node": "3.946.0", - "@smithy/config-resolver": "^4.4.3", - "@smithy/core": "^3.18.7", - "@smithy/fetch-http-handler": "^5.3.6", - "@smithy/hash-node": "^4.2.5", - "@smithy/invalid-dependency": "^4.2.5", - "@smithy/middleware-content-length": "^4.2.5", - "@smithy/middleware-endpoint": "^4.3.14", - "@smithy/middleware-retry": "^4.4.14", - "@smithy/middleware-serde": "^4.2.6", - "@smithy/middleware-stack": "^4.2.5", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/node-http-handler": "^4.4.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/smithy-client": "^4.9.10", - "@smithy/types": "^4.9.0", - "@smithy/url-parser": "^4.2.5", + "@aws-sdk/core": "^3.973.5", + "@aws-sdk/middleware-host-header": "^3.972.3", + "@aws-sdk/middleware-logger": "^3.972.3", + "@aws-sdk/middleware-recursion-detection": "^3.972.3", + "@aws-sdk/middleware-user-agent": "^3.972.5", + "@aws-sdk/region-config-resolver": "^3.972.3", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-endpoints": "3.980.0", + "@aws-sdk/util-user-agent-browser": "^3.972.3", + "@aws-sdk/util-user-agent-node": "^3.972.3", + "@smithy/config-resolver": "^4.4.6", + "@smithy/core": "^3.22.0", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/hash-node": "^4.2.8", + "@smithy/invalid-dependency": "^4.2.8", + "@smithy/middleware-content-length": "^4.2.8", + "@smithy/middleware-endpoint": "^4.4.12", + "@smithy/middleware-retry": "^4.4.29", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/node-http-handler": "^4.4.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.11.1", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.13", - "@smithy/util-defaults-mode-node": "^4.2.16", - "@smithy/util-endpoints": "^3.2.5", - "@smithy/util-middleware": "^4.2.5", - "@smithy/util-retry": "^4.2.5", + "@smithy/util-defaults-mode-browser": "^4.3.28", + "@smithy/util-defaults-mode-node": "^4.2.31", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/core": { - "version": "3.946.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.946.0.tgz", - "integrity": "sha512-u2BkbLLVbMFrEiXrko2+S6ih5sUZPlbVyRPtXOqMHlCyzr70sE8kIiD6ba223rQeIFPcYfW/wHc6k4ihW2xxVg==", - "dev": true, + "version": "3.973.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.5.tgz", + "integrity": "sha512-IMM7xGfLGW6lMvubsA4j6BHU5FPgGAxoQ/NA63KqNLMwTS+PeMBcx8DPHL12Vg6yqOZnqok9Mu4H2BdQyq7gSA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.936.0", - "@aws-sdk/xml-builder": "3.930.0", - "@smithy/core": "^3.18.7", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/property-provider": "^4.2.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/signature-v4": "^5.3.5", - "@smithy/smithy-client": "^4.9.10", - "@smithy/types": "^4.9.0", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/xml-builder": "^3.972.2", + "@smithy/core": "^3.22.0", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/signature-v4": "^5.3.8", + "@smithy/smithy-client": "^4.11.1", + "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", - "@smithy/util-middleware": "^4.2.5", + "@smithy/util-middleware": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/crc64-nvme": { - "version": "3.969.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/crc64-nvme/-/crc64-nvme-3.969.0.tgz", - "integrity": "sha512-IGNkP54HD3uuLnrPCYsv3ZD478UYq+9WwKrIVJ9Pdi3hxPg8562CH3ZHf8hEgfePN31P9Kj+Zu9kq2Qcjjt61A==", + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/crc64-nvme/-/crc64-nvme-3.972.0.tgz", + "integrity": "sha512-ThlLhTqX68jvoIVv+pryOdb5coP1cX1/MaTbB9xkGDCbWbsqQcLqzPxuSoW1DCnAAIacmXCWpzUNOB9pv+xXQw==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.12.0", @@ -1112,179 +548,171 @@ } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.946.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.946.0.tgz", - "integrity": "sha512-P4l+K6wX1tf8LmWUvZofdQ+BgCNyk6Tb9u1H10npvqpuCD+dCM4pXIBq3PQcv/juUBOvLGGREo+Govuh3lfD0Q==", - "dev": true, + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.3.tgz", + "integrity": "sha512-OBYNY4xQPq7Rx+oOhtyuyO0AQvdJSpXRg7JuPNBJH4a1XXIzJQl4UHQTPKZKwfJXmYLpv4+OkcFen4LYmDPd3g==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.946.0", - "@aws-sdk/types": "3.936.0", - "@smithy/property-provider": "^4.2.5", - "@smithy/types": "^4.9.0", + "@aws-sdk/core": "^3.973.5", + "@aws-sdk/types": "^3.973.1", + "@smithy/property-provider": "^4.2.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.946.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.946.0.tgz", - "integrity": "sha512-/zeOJ6E7dGZQ/l2k7KytEoPJX0APIhwt0A79hPf/bUpMF4dDs2P6JmchDrotk0a0Y/MIdNF8sBQ/MEOPnBiYoQ==", - "dev": true, + "version": "3.972.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.5.tgz", + "integrity": "sha512-GpvBgEmSZPvlDekd26Zi+XsI27Qz7y0utUx0g2fSTSiDzhnd1FSa1owuodxR0BcUKNL7U2cOVhhDxgZ4iSoPVg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.946.0", - "@aws-sdk/types": "3.936.0", - "@smithy/fetch-http-handler": "^5.3.6", - "@smithy/node-http-handler": "^4.4.5", - "@smithy/property-provider": "^4.2.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/smithy-client": "^4.9.10", - "@smithy/types": "^4.9.0", - "@smithy/util-stream": "^4.5.6", + "@aws-sdk/core": "^3.973.5", + "@aws-sdk/types": "^3.973.1", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/node-http-handler": "^4.4.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.11.1", + "@smithy/types": "^4.12.0", + "@smithy/util-stream": "^4.5.10", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.946.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.946.0.tgz", - "integrity": "sha512-Pdgcra3RivWj/TuZmfFaHbqsvvgnSKO0CxlRUMMr0PgBiCnUhyl+zBktdNOeGsOPH2fUzQpYhcUjYUgVSdcSDQ==", - "dev": true, + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.3.tgz", + "integrity": "sha512-rMQAIxstP7cLgYfsRGrGOlpyMl0l8JL2mcke3dsIPLWke05zKOFyR7yoJzWCsI/QiIxjRbxpvPiAeKEA6CoYkg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.946.0", - "@aws-sdk/credential-provider-env": "3.946.0", - "@aws-sdk/credential-provider-http": "3.946.0", - "@aws-sdk/credential-provider-login": "3.946.0", - "@aws-sdk/credential-provider-process": "3.946.0", - "@aws-sdk/credential-provider-sso": "3.946.0", - "@aws-sdk/credential-provider-web-identity": "3.946.0", - "@aws-sdk/nested-clients": "3.946.0", - "@aws-sdk/types": "3.936.0", - "@smithy/credential-provider-imds": "^4.2.5", - "@smithy/property-provider": "^4.2.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", + "@aws-sdk/core": "^3.973.5", + "@aws-sdk/credential-provider-env": "^3.972.3", + "@aws-sdk/credential-provider-http": "^3.972.5", + "@aws-sdk/credential-provider-login": "^3.972.3", + "@aws-sdk/credential-provider-process": "^3.972.3", + "@aws-sdk/credential-provider-sso": "^3.972.3", + "@aws-sdk/credential-provider-web-identity": "^3.972.3", + "@aws-sdk/nested-clients": "3.980.0", + "@aws-sdk/types": "^3.973.1", + "@smithy/credential-provider-imds": "^4.2.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/credential-provider-login": { - "version": "3.946.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.946.0.tgz", - "integrity": "sha512-5iqLNc15u2Zx+7jOdQkIbP62N7n2031tw5hkmIG0DLnozhnk64osOh2CliiOE9x3c4P9Pf4frAwgyy9GzNTk2g==", - "dev": true, + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.3.tgz", + "integrity": "sha512-Gc3O91iVvA47kp2CLIXOwuo5ffo1cIpmmyIewcYjAcvurdFHQ8YdcBe1KHidnbbBO4/ZtywGBACsAX5vr3UdoA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.946.0", - "@aws-sdk/nested-clients": "3.946.0", - "@aws-sdk/types": "3.936.0", - "@smithy/property-provider": "^4.2.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", + "@aws-sdk/core": "^3.973.5", + "@aws-sdk/nested-clients": "3.980.0", + "@aws-sdk/types": "^3.973.1", + "@smithy/property-provider": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.946.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.946.0.tgz", - "integrity": "sha512-I7URUqnBPng1a5y81OImxrwERysZqMBREG6svhhGeZgxmqcpAZ8z5ywILeQXdEOCuuES8phUp/ojzxFjPXp/eA==", - "dev": true, + "version": "3.972.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.4.tgz", + "integrity": "sha512-UwerdzosMSY7V5oIZm3NsMDZPv2aSVzSkZxYxIOWHBeKTZlUqW7XpHtJMZ4PZpJ+HMRhgP+MDGQx4THndgqJfQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "3.946.0", - "@aws-sdk/credential-provider-http": "3.946.0", - "@aws-sdk/credential-provider-ini": "3.946.0", - "@aws-sdk/credential-provider-process": "3.946.0", - "@aws-sdk/credential-provider-sso": "3.946.0", - "@aws-sdk/credential-provider-web-identity": "3.946.0", - "@aws-sdk/types": "3.936.0", - "@smithy/credential-provider-imds": "^4.2.5", - "@smithy/property-provider": "^4.2.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", + "@aws-sdk/credential-provider-env": "^3.972.3", + "@aws-sdk/credential-provider-http": "^3.972.5", + "@aws-sdk/credential-provider-ini": "^3.972.3", + "@aws-sdk/credential-provider-process": "^3.972.3", + "@aws-sdk/credential-provider-sso": "^3.972.3", + "@aws-sdk/credential-provider-web-identity": "^3.972.3", + "@aws-sdk/types": "^3.973.1", + "@smithy/credential-provider-imds": "^4.2.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.946.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.946.0.tgz", - "integrity": "sha512-GtGHX7OGqIeVQ3DlVm5RRF43Qmf3S1+PLJv9svrdvAhAdy2bUb044FdXXqrtSsIfpzTKlHgQUiRo5MWLd35Ntw==", - "dev": true, + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.3.tgz", + "integrity": "sha512-xkSY7zjRqeVc6TXK2xr3z1bTLm0wD8cj3lAkproRGaO4Ku7dPlKy843YKnHrUOUzOnMezdZ4xtmFc0eKIDTo2w==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.946.0", - "@aws-sdk/types": "3.936.0", - "@smithy/property-provider": "^4.2.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", + "@aws-sdk/core": "^3.973.5", + "@aws-sdk/types": "^3.973.1", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.946.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.946.0.tgz", - "integrity": "sha512-LeGSSt2V5iwYey1ENGY75RmoDP3bA2iE/py8QBKW8EDA8hn74XBLkprhrK5iccOvU3UGWY8WrEKFAFGNjJOL9g==", - "dev": true, + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.3.tgz", + "integrity": "sha512-8Ww3F5Ngk8dZ6JPL/V5LhCU1BwMfQd3tLdoEuzaewX8FdnT633tPr+KTHySz9FK7fFPcz5qG3R5edVEhWQD4AA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-sso": "3.946.0", - "@aws-sdk/core": "3.946.0", - "@aws-sdk/token-providers": "3.946.0", - "@aws-sdk/types": "3.936.0", - "@smithy/property-provider": "^4.2.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", + "@aws-sdk/client-sso": "3.980.0", + "@aws-sdk/core": "^3.973.5", + "@aws-sdk/token-providers": "3.980.0", + "@aws-sdk/types": "^3.973.1", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.946.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.946.0.tgz", - "integrity": "sha512-ocBCvjWfkbjxElBI1QUxOnHldsNhoU0uOICFvuRDAZAoxvypJHN3m5BJkqb7gqorBbcv3LRgmBdEnWXOAvq+7Q==", - "dev": true, + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.3.tgz", + "integrity": "sha512-62VufdcH5rRfiRKZRcf1wVbbt/1jAntMj1+J0qAd+r5pQRg2t0/P9/Rz16B1o5/0Se9lVL506LRjrhIJAhYBfA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.946.0", - "@aws-sdk/nested-clients": "3.946.0", - "@aws-sdk/types": "3.936.0", - "@smithy/property-provider": "^4.2.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", + "@aws-sdk/core": "^3.973.5", + "@aws-sdk/nested-clients": "3.980.0", + "@aws-sdk/types": "^3.973.1", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/middleware-bucket-endpoint": { - "version": "3.969.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.969.0.tgz", - "integrity": "sha512-MlbrlixtkTVhYhoasblKOkr7n2yydvUZjjxTnBhIuHmkyBS1619oGnTfq/uLeGYb4NYXdeQ5OYcqsRGvmWSuTw==", + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.972.3.tgz", + "integrity": "sha512-fmbgWYirF67YF1GfD7cg5N6HHQ96EyRNx/rDIrTF277/zTWVuPI2qS/ZHgofwR1NZPe/NWvoppflQY01LrbVLg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.969.0", - "@aws-sdk/util-arn-parser": "3.968.0", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-arn-parser": "^3.972.2", "@smithy/node-config-provider": "^4.3.8", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", @@ -1295,38 +723,13 @@ "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/middleware-bucket-endpoint/node_modules/@aws-sdk/types": { - "version": "3.969.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.969.0.tgz", - "integrity": "sha512-7IIzM5TdiXn+VtgPdVLjmE6uUBUtnga0f4RiSEI1WW10RPuNvZ9U+pL3SwDiRDAdoGrOF9tSLJOFZmfuwYuVYQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-bucket-endpoint/node_modules/@aws-sdk/util-arn-parser": { - "version": "3.968.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.968.0.tgz", - "integrity": "sha512-gqqvYcitIIM2K4lrDX9de9YvOfXBcVdxfT/iLnvHJd4YHvSXlt+gs+AsL4FfPCxG4IG9A+FyulP9Sb1MEA75vw==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, "node_modules/@aws-sdk/middleware-expect-continue": { - "version": "3.969.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.969.0.tgz", - "integrity": "sha512-qXygzSi8osok7tH9oeuS3HoKw6jRfbvg5Me/X5RlHOvSSqQz8c5O9f3MjUApaCUSwbAU92KrbZWasw2PKiaVHg==", + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.972.3.tgz", + "integrity": "sha512-4msC33RZsXQpUKR5QR4HnvBSNCPLGHmB55oDiROqqgyOc+TOfVu2xgi5goA7ms6MdZLeEh2905UfWMnMMF4mRg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.969.0", + "@aws-sdk/types": "^3.973.1", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" @@ -1335,31 +738,18 @@ "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/middleware-expect-continue/node_modules/@aws-sdk/types": { - "version": "3.969.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.969.0.tgz", - "integrity": "sha512-7IIzM5TdiXn+VtgPdVLjmE6uUBUtnga0f4RiSEI1WW10RPuNvZ9U+pL3SwDiRDAdoGrOF9tSLJOFZmfuwYuVYQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, "node_modules/@aws-sdk/middleware-flexible-checksums": { - "version": "3.971.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.971.0.tgz", - "integrity": "sha512-+hGUDUxeIw8s2kkjfeXym0XZxdh0cqkHkDpEanWYdS1gnWkIR+gf9u/DKbKqGHXILPaqHXhWpLTQTVlaB4sI7Q==", + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.972.3.tgz", + "integrity": "sha512-MkNGJ6qB9kpsLwL18kC/ZXppsJbftHVGCisqpEVbTQsum8CLYDX1Bmp/IvhRGNxsqCO2w9/4PwhDKBjG3Uvr4Q==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", "@aws-crypto/util": "5.2.0", - "@aws-sdk/core": "3.970.0", - "@aws-sdk/crc64-nvme": "3.969.0", - "@aws-sdk/types": "3.969.0", + "@aws-sdk/core": "^3.973.5", + "@aws-sdk/crc64-nvme": "3.972.0", + "@aws-sdk/types": "^3.973.1", "@smithy/is-array-buffer": "^4.2.0", "@smithy/node-config-provider": "^4.3.8", "@smithy/protocol-http": "^5.3.8", @@ -1373,93 +763,28 @@ "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@aws-sdk/core": { - "version": "3.970.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.970.0.tgz", - "integrity": "sha512-klpzObldOq8HXzDjDlY6K8rMhYZU6mXRz6P9F9N+tWnjoYFfeBMra8wYApydElTUYQKP1O7RLHwH1OKFfKcqIA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.969.0", - "@aws-sdk/xml-builder": "3.969.0", - "@smithy/core": "^3.20.6", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/signature-v4": "^5.3.8", - "@smithy/smithy-client": "^4.10.8", - "@smithy/types": "^4.12.0", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@aws-sdk/types": { - "version": "3.969.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.969.0.tgz", - "integrity": "sha512-7IIzM5TdiXn+VtgPdVLjmE6uUBUtnga0f4RiSEI1WW10RPuNvZ9U+pL3SwDiRDAdoGrOF9tSLJOFZmfuwYuVYQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@aws-sdk/xml-builder": { - "version": "3.969.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.969.0.tgz", - "integrity": "sha512-BSe4Lx/qdRQQdX8cSSI7Et20vqBspzAjBy8ZmXVoyLkol3y4sXBXzn+BiLtR+oh60ExQn6o2DU4QjdOZbXaKIQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "fast-xml-parser": "5.2.5", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.936.0.tgz", - "integrity": "sha512-tAaObaAnsP1XnLGndfkGWFuzrJYuk9W0b/nLvol66t8FZExIAf/WdkT2NNAWOYxljVs++oHnyHBCxIlaHrzSiw==", - "dev": true, + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.3.tgz", + "integrity": "sha512-aknPTb2M+G3s+0qLCx4Li/qGZH8IIYjugHMv15JTYMe6mgZO8VBpYgeGYsNMGCqCZOcWzuf900jFBG5bopfzmA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.936.0", - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", + "@aws-sdk/types": "^3.973.1", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/middleware-location-constraint": { - "version": "3.969.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.969.0.tgz", - "integrity": "sha512-zH7pDfMLG/C4GWMOpvJEoYcSpj7XsNP9+irlgqwi667sUQ6doHQJ3yyDut3yiTk0maq1VgmriPFELyI9lrvH/g==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.969.0", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-location-constraint/node_modules/@aws-sdk/types": { - "version": "3.969.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.969.0.tgz", - "integrity": "sha512-7IIzM5TdiXn+VtgPdVLjmE6uUBUtnga0f4RiSEI1WW10RPuNvZ9U+pL3SwDiRDAdoGrOF9tSLJOFZmfuwYuVYQ==", + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.972.3.tgz", + "integrity": "sha512-nIg64CVrsXp67vbK0U1/Is8rik3huS3QkRHn2DRDx4NldrEFMgdkZGI/+cZMKD9k4YOS110Dfu21KZLHrFA/1g==", "license": "Apache-2.0", "dependencies": { + "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, @@ -1468,70 +793,12 @@ } }, "node_modules/@aws-sdk/middleware-logger": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.936.0.tgz", - "integrity": "sha512-aPSJ12d3a3Ea5nyEnLbijCaaYJT2QjQ9iW+zGh5QcZYXmOGWbKVyPSxmVOboZQG+c1M8t6d2O7tqrwzIq8L8qw==", - "dev": true, + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.3.tgz", + "integrity": "sha512-Ftg09xNNRqaz9QNzlfdQWfpqMCJbsQdnZVJP55jfhbKi1+FTWxGuvfPoBhDHIovqWKjqbuiew3HuhxbJ0+OjgA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.936.0", - "@smithy/types": "^4.9.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.936.0.tgz", - "integrity": "sha512-l4aGbHpXM45YNgXggIux1HgsCVAvvBoqHPkqLnqMl9QVapfuSTjJHfDYDsx1Xxct6/m7qSMUzanBALhiaGO2fA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.936.0", - "@aws/lambda-invoke-store": "^0.2.0", - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.946.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.946.0.tgz", - "integrity": "sha512-0UTFmFd8PX2k/jLu/DBmR+mmLQWAtUGHYps9Rjx3dcXNwaMLaa/39NoV3qn7Dwzfpqc6JZlZzBk+NDOCJIHW9g==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.946.0", - "@aws-sdk/types": "3.936.0", - "@aws-sdk/util-arn-parser": "3.893.0", - "@smithy/core": "^3.18.7", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/signature-v4": "^5.3.5", - "@smithy/smithy-client": "^4.9.10", - "@smithy/types": "^4.9.0", - "@smithy/util-config-provider": "^4.2.0", - "@smithy/util-middleware": "^4.2.5", - "@smithy/util-stream": "^4.5.6", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/middleware-ssec": { - "version": "3.971.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.971.0.tgz", - "integrity": "sha512-QGVhvRveYG64ZhnS/b971PxXM6N2NU79Fxck4EfQ7am8v1Br0ctoeDDAn9nXNblLGw87we9Z65F7hMxxiFHd3w==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.969.0", + "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, @@ -1539,12 +806,54 @@ "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/middleware-ssec/node_modules/@aws-sdk/types": { - "version": "3.969.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.969.0.tgz", - "integrity": "sha512-7IIzM5TdiXn+VtgPdVLjmE6uUBUtnga0f4RiSEI1WW10RPuNvZ9U+pL3SwDiRDAdoGrOF9tSLJOFZmfuwYuVYQ==", + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.3.tgz", + "integrity": "sha512-PY57QhzNuXHnwbJgbWYTrqIDHYSeOlhfYERTAuc16LKZpTZRJUjzBFokp9hF7u1fuGeE3D70ERXzdbMBOqQz7Q==", "license": "Apache-2.0", "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@aws/lambda-invoke-store": "^0.2.2", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.972.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.5.tgz", + "integrity": "sha512-3IgeIDiQ15tmMBFIdJ1cTy3A9rXHGo+b9p22V38vA3MozeMyVC8VmCYdDLA0iMWo4VHA9LDJTgCM0+xU3wjBOg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.5", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-arn-parser": "^3.972.2", + "@smithy/core": "^3.22.0", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/signature-v4": "^5.3.8", + "@smithy/smithy-client": "^4.11.1", + "@smithy/types": "^4.12.0", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-stream": "^4.5.10", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-ssec": { + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.972.3.tgz", + "integrity": "sha512-dU6kDuULN3o3jEHcjm0c4zWJlY1zWVkjG9NPe9qxYLLpcbdj5kRYBS2DdWYD+1B9f910DezRuws7xDEqKkHQIg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, @@ -1553,169 +862,162 @@ } }, "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.946.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.946.0.tgz", - "integrity": "sha512-7QcljCraeaWQNuqmOoAyZs8KpZcuhPiqdeeKoRd397jVGNRehLFsZbIMOvwaluUDFY11oMyXOkQEERe1Zo2fCw==", - "dev": true, + "version": "3.972.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.5.tgz", + "integrity": "sha512-TVZQ6PWPwQbahUI8V+Er+gS41ctIawcI/uMNmQtQ7RMcg3JYn6gyKAFKUb3HFYx2OjYlx1u11sETSwwEUxVHTg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.946.0", - "@aws-sdk/types": "3.936.0", - "@aws-sdk/util-endpoints": "3.936.0", - "@smithy/core": "^3.18.7", - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", + "@aws-sdk/core": "^3.973.5", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-endpoints": "3.980.0", + "@smithy/core": "^3.22.0", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/nested-clients": { - "version": "3.946.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.946.0.tgz", - "integrity": "sha512-rjAtEguukeW8mlyEQMQI56vxFoyWlaNwowmz1p1rav948SUjtrzjHAp4TOQWhibb7AR7BUTHBCgIcyCRjBEf4g==", - "dev": true, + "version": "3.980.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.980.0.tgz", + "integrity": "sha512-/dONY5xc5/CCKzOqHZCTidtAR4lJXWkGefXvTRKdSKMGaYbbKsxDckisd6GfnvPSLxWtvQzwgRGRutMRoYUApQ==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.946.0", - "@aws-sdk/middleware-host-header": "3.936.0", - "@aws-sdk/middleware-logger": "3.936.0", - "@aws-sdk/middleware-recursion-detection": "3.936.0", - "@aws-sdk/middleware-user-agent": "3.946.0", - "@aws-sdk/region-config-resolver": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@aws-sdk/util-endpoints": "3.936.0", - "@aws-sdk/util-user-agent-browser": "3.936.0", - "@aws-sdk/util-user-agent-node": "3.946.0", - "@smithy/config-resolver": "^4.4.3", - "@smithy/core": "^3.18.7", - "@smithy/fetch-http-handler": "^5.3.6", - "@smithy/hash-node": "^4.2.5", - "@smithy/invalid-dependency": "^4.2.5", - "@smithy/middleware-content-length": "^4.2.5", - "@smithy/middleware-endpoint": "^4.3.14", - "@smithy/middleware-retry": "^4.4.14", - "@smithy/middleware-serde": "^4.2.6", - "@smithy/middleware-stack": "^4.2.5", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/node-http-handler": "^4.4.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/smithy-client": "^4.9.10", - "@smithy/types": "^4.9.0", - "@smithy/url-parser": "^4.2.5", + "@aws-sdk/core": "^3.973.5", + "@aws-sdk/middleware-host-header": "^3.972.3", + "@aws-sdk/middleware-logger": "^3.972.3", + "@aws-sdk/middleware-recursion-detection": "^3.972.3", + "@aws-sdk/middleware-user-agent": "^3.972.5", + "@aws-sdk/region-config-resolver": "^3.972.3", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-endpoints": "3.980.0", + "@aws-sdk/util-user-agent-browser": "^3.972.3", + "@aws-sdk/util-user-agent-node": "^3.972.3", + "@smithy/config-resolver": "^4.4.6", + "@smithy/core": "^3.22.0", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/hash-node": "^4.2.8", + "@smithy/invalid-dependency": "^4.2.8", + "@smithy/middleware-content-length": "^4.2.8", + "@smithy/middleware-endpoint": "^4.4.12", + "@smithy/middleware-retry": "^4.4.29", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/node-http-handler": "^4.4.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.11.1", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.13", - "@smithy/util-defaults-mode-node": "^4.2.16", - "@smithy/util-endpoints": "^3.2.5", - "@smithy/util-middleware": "^4.2.5", - "@smithy/util-retry": "^4.2.5", + "@smithy/util-defaults-mode-browser": "^4.3.28", + "@smithy/util-defaults-mode-node": "^4.2.31", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/region-config-resolver": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.936.0.tgz", - "integrity": "sha512-wOKhzzWsshXGduxO4pqSiNyL9oUtk4BEvjWm9aaq6Hmfdoydq6v6t0rAGHWPjFwy9z2haovGRi3C8IxdMB4muw==", - "dev": true, + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.3.tgz", + "integrity": "sha512-v4J8qYAWfOMcZ4MJUyatntOicTzEMaU7j3OpkRCGGFSL2NgXQ5VbxauIyORA+pxdKZ0qQG2tCQjQjZDlXEC3Ow==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.936.0", - "@smithy/config-resolver": "^4.4.3", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/types": "^4.9.0", + "@aws-sdk/types": "^3.973.1", + "@smithy/config-resolver": "^4.4.6", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.946.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.946.0.tgz", - "integrity": "sha512-61FZ685lKiJuQ06g6U7K3PL9EwKCxNm51wNlxyKV57nnl1GrLD0NC8O3/hDNkCQLNBArT9y3IXl2H7TtIxP8Jg==", - "dev": true, + "version": "3.980.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.980.0.tgz", + "integrity": "sha512-tO2jBj+ZIVM0nEgi1SyxWtaYGpuAJdsrugmWcI3/U2MPWCYsrvKasUo0026NvJJao38wyUq9B8XTG8Xu53j/VA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-sdk-s3": "3.946.0", - "@aws-sdk/types": "3.936.0", - "@smithy/protocol-http": "^5.3.5", - "@smithy/signature-v4": "^5.3.5", - "@smithy/types": "^4.9.0", + "@aws-sdk/middleware-sdk-s3": "^3.972.5", + "@aws-sdk/types": "^3.973.1", + "@smithy/protocol-http": "^5.3.8", + "@smithy/signature-v4": "^5.3.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.946.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.946.0.tgz", - "integrity": "sha512-a5c+rM6CUPX2ExmUZ3DlbLlS5rQr4tbdoGcgBsjnAHiYx8MuMNAI+8M7wfjF13i2yvUQj5WEIddvLpayfEZj9g==", - "dev": true, + "version": "3.980.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.980.0.tgz", + "integrity": "sha512-1nFileg1wAgDmieRoj9dOawgr2hhlh7xdvcH57b1NnqfPaVlcqVJyPc6k3TLDUFPY69eEwNxdGue/0wIz58vjA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.946.0", - "@aws-sdk/nested-clients": "3.946.0", - "@aws-sdk/types": "3.936.0", - "@smithy/property-provider": "^4.2.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", + "@aws-sdk/core": "^3.973.5", + "@aws-sdk/nested-clients": "3.980.0", + "@aws-sdk/types": "^3.973.1", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/types": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.936.0.tgz", - "integrity": "sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg==", + "version": "3.973.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.1.tgz", + "integrity": "sha512-DwHBiMNOB468JiX6+i34c+THsKHErYUdNQ3HexeXZvVn4zouLjgaS4FejiGSi2HyBuzuyHg7SuOPmjSvoU9NRg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/util-arn-parser": { - "version": "3.893.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.893.0.tgz", - "integrity": "sha512-u8H4f2Zsi19DGnwj5FSZzDMhytYF/bCh37vAtBsn3cNDL3YG578X5oc+wSX54pM3tOxS+NY7tvOAo52SW7koUA==", - "dev": true, + "version": "3.972.2", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.972.2.tgz", + "integrity": "sha512-VkykWbqMjlSgBFDyrY3nOSqupMc6ivXuGmvci6Q3NnLq5kC+mKQe2QBZ4nrWRE/jqOxeFP2uYzLtwncYYcvQDg==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/util-endpoints": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.936.0.tgz", - "integrity": "sha512-0Zx3Ntdpu+z9Wlm7JKUBOzS9EunwKAb4KdGUQQxDqh5Lc3ta5uBoub+FgmVuzwnmBu9U1Os8UuwVTH0Lgu+P5w==", - "dev": true, + "version": "3.980.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.980.0.tgz", + "integrity": "sha512-AjKBNEc+rjOZQE1HwcD9aCELqg1GmUj1rtICKuY8cgwB73xJ4U/kNyqKKpN2k9emGqlfDY2D8itIp/vDc6OKpw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.936.0", - "@smithy/types": "^4.9.0", - "@smithy/url-parser": "^4.2.5", - "@smithy/util-endpoints": "^3.2.5", + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-endpoints": "^3.2.8", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/util-locate-window": { @@ -1731,33 +1033,31 @@ } }, "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.936.0.tgz", - "integrity": "sha512-eZ/XF6NxMtu+iCma58GRNRxSq4lHo6zHQLOZRIeL/ghqYJirqHdenMOwrzPettj60KWlv827RVebP9oNVrwZbw==", - "dev": true, + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.3.tgz", + "integrity": "sha512-JurOwkRUcXD/5MTDBcqdyQ9eVedtAsZgw5rBwktsPTN7QtPiS2Ld1jkJepNgYoCufz1Wcut9iup7GJDoIHp8Fw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.936.0", - "@smithy/types": "^4.9.0", + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.946.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.946.0.tgz", - "integrity": "sha512-a2UwwvzbK5AxHKUBupfg4s7VnkqRAHjYsuezHnKCniczmT4HZfP1NnfwwvLKEH8qaTrwenxjKSfq4UWmWkvG+Q==", - "dev": true, + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.972.3.tgz", + "integrity": "sha512-gqG+02/lXQtO0j3US6EVnxtwwoXQC5l2qkhLCrqUrqdtcQxV7FDMbm9wLjKqoronSHyELGTjbFKK/xV5q1bZNA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-user-agent": "3.946.0", - "@aws-sdk/types": "3.936.0", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/types": "^4.9.0", + "@aws-sdk/middleware-user-agent": "^3.972.5", + "@aws-sdk/types": "^3.973.1", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" }, "peerDependencies": { "aws-crt": ">=1.0.0" @@ -1769,24 +1069,23 @@ } }, "node_modules/@aws-sdk/xml-builder": { - "version": "3.930.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.930.0.tgz", - "integrity": "sha512-YIfkD17GocxdmlUVc3ia52QhcWuRIUJonbF8A2CYfcWNV3HzvAqpcPeC0bYUhkK+8e8YO1ARnLKZQE0TlwzorA==", - "dev": true, + "version": "3.972.2", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.2.tgz", + "integrity": "sha512-jGOOV/bV1DhkkUhHiZ3/1GZ67cZyOXaDb7d1rYD6ZiXf5V9tBNOcgqXwRRPvrCbYaFRa1pPMFb3ZjqjWpR3YfA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@smithy/types": "^4.12.0", "fast-xml-parser": "5.2.5", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws/lambda-invoke-store": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.2.tgz", - "integrity": "sha512-C0NBLsIqzDIae8HFw9YIrIBsbc0xTiOtt7fAukGPnqQ/+zZNaq+4jhuccltK0QuWHBnNm/a6kLIRA6GFiM10eg==", + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.3.tgz", + "integrity": "sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw==", "license": "Apache-2.0", "engines": { "node": ">=18.0.0" @@ -1820,6 +1119,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz", "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.26.2", @@ -2166,9 +1466,9 @@ "license": "MIT" }, "node_modules/@dotenvx/dotenvx": { - "version": "1.51.2", - "resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-1.51.2.tgz", - "integrity": "sha512-+693mNflujDZxudSEqSNGpn92QgFhJlBn9q2mDQ9yGWyHuz3hZ8B5g3EXCwdAz4DMJAI+OFCIbfEFZS+YRdrEA==", + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-1.52.0.tgz", + "integrity": "sha512-CaQcc8JvtzQhUSm9877b6V4Tb7HCotkcyud9X2YwdqtQKwgljkMRwU96fVYKnzN3V0Hj74oP7Es+vZ0mS+Aa1w==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -3352,54 +2652,55 @@ "license": "MIT" }, "node_modules/@formatjs/ecma402-abstract": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.6.tgz", - "integrity": "sha512-HJnTFeRM2kVFVr5gr5kH1XP6K0JcJtE7Lzvtr3FS/so5f1kpsqqqxy5JF+FRaO6H2qmcMfAUIox7AJteieRtVw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-3.1.1.tgz", + "integrity": "sha512-jhZbTwda+2tcNrs4kKvxrPLPjx8QsBCLCUgrrJ/S+G9YrGHWLhAyFMMBHJBnBoOwuLHd7L14FgYudviKaxkO2Q==", "license": "MIT", "dependencies": { - "@formatjs/fast-memoize": "2.2.7", - "@formatjs/intl-localematcher": "0.6.2", - "decimal.js": "^10.4.3", - "tslib": "^2.8.0" + "@formatjs/fast-memoize": "3.1.0", + "@formatjs/intl-localematcher": "0.8.1", + "decimal.js": "^10.6.0", + "tslib": "^2.8.1" } }, "node_modules/@formatjs/ecma402-abstract/node_modules/@formatjs/intl-localematcher": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.6.2.tgz", - "integrity": "sha512-XOMO2Hupl0wdd172Y06h6kLpBz6Dv+J4okPLl4LPtzbr8f66WbIoy4ev98EBuZ6ZK4h5ydTN6XneT4QVpD7cdA==", + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.8.1.tgz", + "integrity": "sha512-xwEuwQFdtSq1UKtQnyTZWC+eHdv7Uygoa+H2k/9uzBVQjDyp9r20LNDNKedWXll7FssT3GRHvqsdJGYSUWqYFA==", "license": "MIT", "dependencies": { - "tslib": "^2.8.0" + "@formatjs/fast-memoize": "3.1.0", + "tslib": "^2.8.1" } }, "node_modules/@formatjs/fast-memoize": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.7.tgz", - "integrity": "sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-3.1.0.tgz", + "integrity": "sha512-b5mvSWCI+XVKiz5WhnBCY3RJ4ZwfjAidU0yVlKa3d3MSgKmH1hC3tBGEAtYyN5mqL7N0G5x0BOUYyO8CEupWgg==", "license": "MIT", "dependencies": { - "tslib": "^2.8.0" + "tslib": "^2.8.1" } }, "node_modules/@formatjs/icu-messageformat-parser": { - "version": "2.11.4", - "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.11.4.tgz", - "integrity": "sha512-7kR78cRrPNB4fjGFZg3Rmj5aah8rQj9KPzuLsmcSn4ipLXQvC04keycTI1F7kJYDwIXtT2+7IDEto842CfZBtw==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-3.5.1.tgz", + "integrity": "sha512-sSDmSvmmoVQ92XqWb499KrIhv/vLisJU8ITFrx7T7NZHUmMY7EL9xgRowAosaljhqnj/5iufG24QrdzB6X3ItA==", "license": "MIT", "dependencies": { - "@formatjs/ecma402-abstract": "2.3.6", - "@formatjs/icu-skeleton-parser": "1.8.16", - "tslib": "^2.8.0" + "@formatjs/ecma402-abstract": "3.1.1", + "@formatjs/icu-skeleton-parser": "2.1.1", + "tslib": "^2.8.1" } }, "node_modules/@formatjs/icu-skeleton-parser": { - "version": "1.8.16", - "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.16.tgz", - "integrity": "sha512-H13E9Xl+PxBd8D5/6TVUluSpxGNvFSlN/b3coUp0e0JpuWXXnQDiavIpY3NnvSp4xhEMoXyyBvVfdFX8jglOHQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-2.1.1.tgz", + "integrity": "sha512-PSFABlcNefjI6yyk8f7nyX1DC7NHmq6WaCHZLySEXBrXuLOB2f935YsnzuPjlz+ibhb9yWTdPeVX1OVcj24w2Q==", "license": "MIT", "dependencies": { - "@formatjs/ecma402-abstract": "2.3.6", - "tslib": "^2.8.0" + "@formatjs/ecma402-abstract": "3.1.1", + "tslib": "^2.8.1" } }, "node_modules/@formatjs/intl-localematcher": { @@ -4064,9 +3365,9 @@ "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { - "version": "16.1.0", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.1.0.tgz", - "integrity": "sha512-sooC/k0LCF4/jLXYHpgfzJot04lZQqsttn8XJpTguP8N3GhqXN3wSkh68no2OcZzS/qeGwKDFTqhZ8WofdXmmQ==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.1.6.tgz", + "integrity": "sha512-/Qq3PTagA6+nYVfryAtQ7/9FEr/6YVyvOtl6rZnGsbReGLf0jZU6gkpr1FuChAQpvV46a78p4cmHOVP8mbfSMQ==", "license": "MIT", "dependencies": { "fast-glob": "3.3.1" @@ -4206,6 +3507,7 @@ "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": "^14.21.3 || >=16" }, @@ -5334,9 +4636,9 @@ } }, "node_modules/@posthog/core": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@posthog/core/-/core-1.11.0.tgz", - "integrity": "sha512-BnUQ9FP5vqMr2NKntDSLfMCwO/pOI2In7kAjg6vLVzU1JdcPt266kwCZj84PTYbdSfHG5ELDs3hXNv9Rn+coUw==", + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/@posthog/core/-/core-1.17.0.tgz", + "integrity": "sha512-8pDNL+/u9ojzXloA5wILVDXBCV5daJ7w2ipCALQlEEZmL752cCKhRpbyiHn3tjKXh3Hy6aOboJneYa1JdlVHrQ==", "license": "MIT", "dependencies": { "cross-spawn": "^7.0.6" @@ -7578,91 +6880,94 @@ } }, "node_modules/@react-email/body": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@react-email/body/-/body-0.2.0.tgz", - "integrity": "sha512-9GCWmVmKUAoRfloboCd+RKm6X17xn7eGL7HnpAZUnjBXBilWCxsKnLMTC/ixSHDKS/A/057M1Tx6ZUXd89sVBw==", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@react-email/body/-/body-0.2.1.tgz", + "integrity": "sha512-ljDiQiJDu/Fq//vSIIP0z5Nuvt4+DX1RqGasstChDGJB/14ogd4VdNS9aacoede/ZjGy3o3Qb+cxyS+XgM6SwQ==", "license": "MIT", + "engines": { + "node": ">=20.0.0" + }, "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc" } }, "node_modules/@react-email/button": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@react-email/button/-/button-0.2.0.tgz", - "integrity": "sha512-8i+v6cMxr2emz4ihCrRiYJPp2/sdYsNNsBzXStlcA+/B9Umpm5Jj3WJKYpgTPM+aeyiqlG/MMI1AucnBm4f1oQ==", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@react-email/button/-/button-0.2.1.tgz", + "integrity": "sha512-qXyj7RZLE7POy9BMKSoqQ00tOXThjOZSUnI2Yu9i29IHngPlmrNayIWBoVKtElES7OWwypUcpiajwi1mUWx6/A==", "license": "MIT", "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" }, "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc" } }, "node_modules/@react-email/code-block": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@react-email/code-block/-/code-block-0.2.0.tgz", - "integrity": "sha512-eIrPW9PIFgDopQU0e/OPpwCW2QWQDtNZDSsiN4sJO8KdMnWWnXJicnRfzrit5rHwFo+Y98i+w/Y5ScnBAFr1dQ==", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@react-email/code-block/-/code-block-0.2.1.tgz", + "integrity": "sha512-M3B7JpVH4ytgn83/ujRR1k1DQHvTeABiDM61OvAbjLRPhC/5KLHU5KkzIbbuGIrjWwxAbL1kSQzU8MhLEtSxyw==", "license": "MIT", "dependencies": { "prismjs": "^1.30.0" }, "engines": { - "node": ">=22.0.0" + "node": ">=20.0.0" }, "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc" } }, "node_modules/@react-email/code-inline": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/@react-email/code-inline/-/code-inline-0.0.5.tgz", - "integrity": "sha512-MmAsOzdJpzsnY2cZoPHFPk6uDO/Ncpb4Kh1hAt9UZc1xOW3fIzpe1Pi9y9p6wwUmpaeeDalJxAxH6/fnTquinA==", + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@react-email/code-inline/-/code-inline-0.0.6.tgz", + "integrity": "sha512-jfhebvv3dVsp3OdPgKXnk8+e2pBiDVZejDOBFzBa/IblrAJ9cQDkN6rBD5IyEg8hTOxwbw3iaI/yZFmDmIguIA==", "license": "MIT", "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" }, "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc" } }, "node_modules/@react-email/column": { - "version": "0.0.13", - "resolved": "https://registry.npmjs.org/@react-email/column/-/column-0.0.13.tgz", - "integrity": "sha512-Lqq17l7ShzJG/d3b1w/+lVO+gp2FM05ZUo/nW0rjxB8xBICXOVv6PqjDnn3FXKssvhO5qAV20lHM6S+spRhEwQ==", + "version": "0.0.14", + "resolved": "https://registry.npmjs.org/@react-email/column/-/column-0.0.14.tgz", + "integrity": "sha512-f+W+Bk2AjNO77zynE33rHuQhyqVICx4RYtGX9NKsGUg0wWjdGP0qAuIkhx9Rnmk4/hFMo1fUrtYNqca9fwJdHg==", "license": "MIT", "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" }, "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc" } }, "node_modules/@react-email/components": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@react-email/components/-/components-1.0.2.tgz", - "integrity": "sha512-VKQR/motrySQMvy+ZUwPjdeD9iI9mCt8cfXuJAX8cK16rtzkEe12yq6/pXyW7c6qEMj7d+PNsoAcO+3AbJSfPg==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@react-email/components/-/components-1.0.6.tgz", + "integrity": "sha512-3GwOeq+5yyiAcwSf7TnHi/HWKn22lXbwxQmkkAviSwZLlhsRVxvmWqRxvUVfQk/HclDUG+62+sGz9qjfb2Uxjw==", "license": "MIT", "dependencies": { - "@react-email/body": "0.2.0", - "@react-email/button": "0.2.0", - "@react-email/code-block": "0.2.0", - "@react-email/code-inline": "0.0.5", - "@react-email/column": "0.0.13", - "@react-email/container": "0.0.15", - "@react-email/font": "0.0.9", - "@react-email/head": "0.0.12", - "@react-email/heading": "0.0.15", - "@react-email/hr": "0.0.11", - "@react-email/html": "0.0.11", - "@react-email/img": "0.0.11", - "@react-email/link": "0.0.12", - "@react-email/markdown": "0.0.17", - "@react-email/preview": "0.0.13", - "@react-email/render": "2.0.0", - "@react-email/row": "0.0.12", - "@react-email/section": "0.0.16", - "@react-email/tailwind": "2.0.2", - "@react-email/text": "0.1.5" + "@react-email/body": "0.2.1", + "@react-email/button": "0.2.1", + "@react-email/code-block": "0.2.1", + "@react-email/code-inline": "0.0.6", + "@react-email/column": "0.0.14", + "@react-email/container": "0.0.16", + "@react-email/font": "0.0.10", + "@react-email/head": "0.0.13", + "@react-email/heading": "0.0.16", + "@react-email/hr": "0.0.12", + "@react-email/html": "0.0.12", + "@react-email/img": "0.0.12", + "@react-email/link": "0.0.13", + "@react-email/markdown": "0.0.18", + "@react-email/preview": "0.0.14", + "@react-email/render": "2.0.4", + "@react-email/row": "0.0.13", + "@react-email/section": "0.0.17", + "@react-email/tailwind": "2.0.3", + "@react-email/text": "0.1.6" }, "engines": { "node": ">=20.0.0" @@ -7672,136 +6977,139 @@ } }, "node_modules/@react-email/container": { - "version": "0.0.15", - "resolved": "https://registry.npmjs.org/@react-email/container/-/container-0.0.15.tgz", - "integrity": "sha512-Qo2IQo0ru2kZq47REmHW3iXjAQaKu4tpeq/M8m1zHIVwKduL2vYOBQWbC2oDnMtWPmkBjej6XxgtZByxM6cCFg==", + "version": "0.0.16", + "resolved": "https://registry.npmjs.org/@react-email/container/-/container-0.0.16.tgz", + "integrity": "sha512-QWBB56RkkU0AJ9h+qy33gfT5iuZknPC7Un/IjZv9B0QmMIK+WWacc0cH6y2SV5Cv/b99hU94fjEMOOO4enpkbQ==", "license": "MIT", "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" }, "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc" } }, "node_modules/@react-email/font": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/@react-email/font/-/font-0.0.9.tgz", - "integrity": "sha512-4zjq23oT9APXkerqeslPH3OZWuh5X4crHK6nx82mVHV2SrLba8+8dPEnWbaACWTNjOCbcLIzaC9unk7Wq2MIXw==", + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/@react-email/font/-/font-0.0.10.tgz", + "integrity": "sha512-0urVSgCmQIfx5r7Xc586miBnQUVnGp3OTYUm8m5pwtQRdTRO5XrTtEfNJ3JhYhSOruV0nD8fd+dXtKXobum6tA==", "license": "MIT", + "engines": { + "node": ">=20.0.0" + }, "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc" } }, "node_modules/@react-email/head": { - "version": "0.0.12", - "resolved": "https://registry.npmjs.org/@react-email/head/-/head-0.0.12.tgz", - "integrity": "sha512-X2Ii6dDFMF+D4niNwMAHbTkeCjlYYnMsd7edXOsi0JByxt9wNyZ9EnhFiBoQdqkE+SMDcu8TlNNttMrf5sJeMA==", + "version": "0.0.13", + "resolved": "https://registry.npmjs.org/@react-email/head/-/head-0.0.13.tgz", + "integrity": "sha512-AJg6le/08Gz4tm+6MtKXqtNNyKHzmooOCdmtqmWxD7FxoAdU1eVcizhtQ0gcnVaY6ethEyE/hnEzQxt1zu5Kog==", "license": "MIT", "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" }, "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc" } }, "node_modules/@react-email/heading": { - "version": "0.0.15", - "resolved": "https://registry.npmjs.org/@react-email/heading/-/heading-0.0.15.tgz", - "integrity": "sha512-xF2GqsvBrp/HbRHWEfOgSfRFX+Q8I5KBEIG5+Lv3Vb2R/NYr0s8A5JhHHGf2pWBMJdbP4B2WHgj/VUrhy8dkIg==", + "version": "0.0.16", + "resolved": "https://registry.npmjs.org/@react-email/heading/-/heading-0.0.16.tgz", + "integrity": "sha512-jmsKnQm1ykpBzw4hCYHwBkt5pW2jScXffPeEH5ZRF5tZeF5b1pvlFTO9han7C0pCkZYo1kEvWiRtx69yfCIwuw==", "license": "MIT", "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" }, "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc" } }, "node_modules/@react-email/hr": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/@react-email/hr/-/hr-0.0.11.tgz", - "integrity": "sha512-S1gZHVhwOsd1Iad5IFhpfICwNPMGPJidG/Uysy1AwmspyoAP5a4Iw3OWEpINFdgh9MHladbxcLKO2AJO+cA9Lw==", + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/@react-email/hr/-/hr-0.0.12.tgz", + "integrity": "sha512-TwmOmBDibavUQpXBxpmZYi2Iks/yeZOzFYh+di9EltMSnEabH8dMZXrl+pxNXzCgZ2XE8HY7VmUL65Lenfu5PA==", "license": "MIT", "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" }, "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc" } }, "node_modules/@react-email/html": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/@react-email/html/-/html-0.0.11.tgz", - "integrity": "sha512-qJhbOQy5VW5qzU74AimjAR9FRFQfrMa7dn4gkEXKMB/S9xZN8e1yC1uA9C15jkXI/PzmJ0muDIWmFwatm5/+VA==", + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/@react-email/html/-/html-0.0.12.tgz", + "integrity": "sha512-KTShZesan+UsreU7PDUV90afrZwU5TLwYlALuCSU0OT+/U8lULNNbAUekg+tGwCnOfIKYtpDPKkAMRdYlqUznw==", "license": "MIT", "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" }, "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc" } }, "node_modules/@react-email/img": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/@react-email/img/-/img-0.0.11.tgz", - "integrity": "sha512-aGc8Y6U5C3igoMaqAJKsCpkbm1XjguQ09Acd+YcTKwjnC2+0w3yGUJkjWB2vTx4tN8dCqQCXO8FmdJpMfOA9EQ==", + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/@react-email/img/-/img-0.0.12.tgz", + "integrity": "sha512-sRCpEARNVTf3FQhZOC+JTvu5r6ubiYWkT0ucYXg8ctkyi4G8QG+jgYPiNUqVeTLA2STOfmPM/nrk1nb84y6CPQ==", "license": "MIT", "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" }, "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc" } }, "node_modules/@react-email/link": { - "version": "0.0.12", - "resolved": "https://registry.npmjs.org/@react-email/link/-/link-0.0.12.tgz", - "integrity": "sha512-vF+xxQk2fGS1CN7UPQDbzvcBGfffr+GjTPNiWM38fhBfsLv6A/YUfaqxWlmL7zLzVmo0K2cvvV9wxlSyNba1aQ==", + "version": "0.0.13", + "resolved": "https://registry.npmjs.org/@react-email/link/-/link-0.0.13.tgz", + "integrity": "sha512-lkWc/NjOcefRZMkQoSDDbuKBEBDES9aXnFEOuPH845wD3TxPwh+QTf0fStuzjoRLUZWpHnio4z7qGGRYusn/sw==", "license": "MIT", "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" }, "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc" } }, "node_modules/@react-email/markdown": { - "version": "0.0.17", - "resolved": "https://registry.npmjs.org/@react-email/markdown/-/markdown-0.0.17.tgz", - "integrity": "sha512-6op3AfsBC9BJKkhG+eoMFRFWlr0/f3FYbtQrK+VhGzJocEAY0WINIFN+W8xzXr//3IL0K/aKtnH3FtpIuescQQ==", + "version": "0.0.18", + "resolved": "https://registry.npmjs.org/@react-email/markdown/-/markdown-0.0.18.tgz", + "integrity": "sha512-gSuYK5fsMbGk87jDebqQ6fa2fKcWlkf2Dkva8kMONqLgGCq8/0d+ZQYMEJsdidIeBo3kmsnHZPrwdFB4HgjUXg==", "license": "MIT", "dependencies": { "marked": "^15.0.12" }, "engines": { - "node": ">=22.0.0" + "node": ">=20.0.0" }, "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc" } }, "node_modules/@react-email/preview": { - "version": "0.0.13", - "resolved": "https://registry.npmjs.org/@react-email/preview/-/preview-0.0.13.tgz", - "integrity": "sha512-F7j9FJ0JN/A4d7yr+aw28p4uX7VLWs7hTHtLo7WRyw4G+Lit6Zucq4UWKRxJC8lpsUdzVmG7aBJnKOT+urqs/w==", + "version": "0.0.14", + "resolved": "https://registry.npmjs.org/@react-email/preview/-/preview-0.0.14.tgz", + "integrity": "sha512-aYK8q0IPkBXyMsbpMXgxazwHxYJxTrXrV95GFuu2HbEiIToMwSyUgb8HDFYwPqqfV03/jbwqlsXmFxsOd+VNaw==", "license": "MIT", "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" }, "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc" } }, "node_modules/@react-email/render": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@react-email/render/-/render-2.0.0.tgz", - "integrity": "sha512-rdjNj6iVzv8kRKDPFas+47nnoe6B40+nwukuXwY4FCwM7XBg6tmYr+chQryCuavUj2J65MMf6fztk1bxOUiSVA==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@react-email/render/-/render-2.0.4.tgz", + "integrity": "sha512-kht2oTFQ1SwrLpd882ahTvUtNa9s53CERHstiTbzhm6aR2Hbykp/mQ4tpPvsBGkKAEvKRlDEoooh60Uk6nHK1g==", "license": "MIT", "dependencies": { "html-to-text": "^9.0.5", "prettier": "^3.5.3" }, "engines": { - "node": ">=22.0.0" + "node": ">=20.0.0" }, "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc", @@ -7809,33 +7117,33 @@ } }, "node_modules/@react-email/row": { - "version": "0.0.12", - "resolved": "https://registry.npmjs.org/@react-email/row/-/row-0.0.12.tgz", - "integrity": "sha512-HkCdnEjvK3o+n0y0tZKXYhIXUNPDx+2vq1dJTmqappVHXS5tXS6W5JOPZr5j+eoZ8gY3PShI2LWj5rWF7ZEtIQ==", + "version": "0.0.13", + "resolved": "https://registry.npmjs.org/@react-email/row/-/row-0.0.13.tgz", + "integrity": "sha512-bYnOac40vIKCId7IkwuLAAsa3fKfSfqCvv6epJKmPE0JBuu5qI4FHFCl9o9dVpIIS08s/ub+Y/txoMt0dYziGw==", "license": "MIT", "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" }, "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc" } }, "node_modules/@react-email/section": { - "version": "0.0.16", - "resolved": "https://registry.npmjs.org/@react-email/section/-/section-0.0.16.tgz", - "integrity": "sha512-FjqF9xQ8FoeUZYKSdt8sMIKvoT9XF8BrzhT3xiFKdEMwYNbsDflcjfErJe3jb7Wj/es/lKTbV5QR1dnLzGpL3w==", + "version": "0.0.17", + "resolved": "https://registry.npmjs.org/@react-email/section/-/section-0.0.17.tgz", + "integrity": "sha512-qNl65ye3W0Rd5udhdORzTV9ezjb+GFqQQSae03NDzXtmJq6sqVXNWNiVolAjvJNypim+zGXmv6J9TcV5aNtE/w==", "license": "MIT", "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" }, "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc" } }, "node_modules/@react-email/tailwind": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@react-email/tailwind/-/tailwind-2.0.2.tgz", - "integrity": "sha512-ooi1H77+w+MN3a3Yps66GYTMoo9PvLtzJ1bTEI+Ta58MUUEQOcdxxXPwbnox+xj2kSwv0g/B63qquNTabKI8Bw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@react-email/tailwind/-/tailwind-2.0.3.tgz", + "integrity": "sha512-URXb/T2WS4RlNGM5QwekYnivuiVUcU87H0y5sqLl6/Oi3bMmgL0Bmw/W9GeJylC+876Vw+E6NkE0uRiUFIQwGg==", "license": "MIT", "dependencies": { "tailwindcss": "^4.1.18" @@ -7844,17 +7152,17 @@ "node": ">=20.0.0" }, "peerDependencies": { - "@react-email/body": "0.2.0", - "@react-email/button": "0.2.0", - "@react-email/code-block": "0.2.0", - "@react-email/code-inline": "0.0.5", - "@react-email/container": "0.0.15", - "@react-email/heading": "0.0.15", - "@react-email/hr": "0.0.11", - "@react-email/img": "0.0.11", - "@react-email/link": "0.0.12", - "@react-email/preview": "0.0.13", - "@react-email/text": "0.1.5", + "@react-email/body": "0.2.1", + "@react-email/button": "0.2.1", + "@react-email/code-block": "0.2.1", + "@react-email/code-inline": "0.0.6", + "@react-email/container": "0.0.16", + "@react-email/heading": "0.0.16", + "@react-email/hr": "0.0.12", + "@react-email/img": "0.0.12", + "@react-email/link": "0.0.13", + "@react-email/preview": "0.0.14", + "@react-email/text": "0.1.6", "react": "^18.0 || ^19.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { @@ -7891,12 +7199,13 @@ } }, "node_modules/@react-email/text": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@react-email/text/-/text-0.1.5.tgz", - "integrity": "sha512-o5PNHFSE085VMXayxH+SJ1LSOtGsTv+RpNKnTiJDrJUwoBu77G3PlKOsZZQHCNyD28WsQpl9v2WcJLbQudqwPg==", + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@react-email/text/-/text-0.1.6.tgz", + "integrity": "sha512-TYqkioRS45wTR5il3dYk/SbUjjEdhSwh9BtRNB99qNH1pXAwA45H7rAuxehiu8iJQJH0IyIr+6n62gBz9ezmsw==", "license": "MIT", + "peer": true, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" }, "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc" @@ -8045,9 +7354,9 @@ } }, "node_modules/@smithy/core": { - "version": "3.20.7", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.20.7.tgz", - "integrity": "sha512-aO7jmh3CtrmPsIJxUwYIzI5WVlMK8BMCPQ4D4nTzqTqBhbzvxHNzBMGcEg13yg/z9R2Qsz49NUFl0F0lVbTVFw==", + "version": "3.22.1", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.22.1.tgz", + "integrity": "sha512-x3ie6Crr58MWrm4viHqqy2Du2rHYZjwu8BekasrQx4ca+Y24dzVAwq3yErdqIbc2G3I0kLQA13PQ+/rde+u65g==", "license": "Apache-2.0", "dependencies": { "@smithy/middleware-serde": "^4.2.9", @@ -8056,7 +7365,7 @@ "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-middleware": "^4.2.8", - "@smithy/util-stream": "^4.5.10", + "@smithy/util-stream": "^4.5.11", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" @@ -8265,12 +7574,12 @@ } }, "node_modules/@smithy/middleware-endpoint": { - "version": "4.4.8", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.8.tgz", - "integrity": "sha512-TV44qwB/T0OMMzjIuI+JeS0ort3bvlPJ8XIH0MSlGADraXpZqmyND27ueuAL3E14optleADWqtd7dUgc2w+qhQ==", + "version": "4.4.13", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.13.tgz", + "integrity": "sha512-x6vn0PjYmGdNuKh/juUJJewZh7MoQ46jYaJ2mvekF4EesMuFfrl4LaW/k97Zjf8PTCPQmPgMvwewg7eNoH9n5w==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.20.7", + "@smithy/core": "^3.22.1", "@smithy/middleware-serde": "^4.2.9", "@smithy/node-config-provider": "^4.3.8", "@smithy/shared-ini-file-loader": "^4.4.3", @@ -8284,15 +7593,15 @@ } }, "node_modules/@smithy/middleware-retry": { - "version": "4.4.24", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.24.tgz", - "integrity": "sha512-yiUY1UvnbUFfP5izoKLtfxDSTRv724YRRwyiC/5HYY6vdsVDcDOXKSXmkJl/Hovcxt5r+8tZEUAdrOaCJwrl9Q==", + "version": "4.4.30", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.30.tgz", + "integrity": "sha512-CBGyFvN0f8hlnqKH/jckRDz78Snrp345+PVk8Ux7pnkUCW97Iinse59lY78hBt04h1GZ6hjBN94BRwZy1xC8Bg==", "license": "Apache-2.0", "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/protocol-http": "^5.3.8", "@smithy/service-error-classification": "^4.2.8", - "@smithy/smithy-client": "^4.10.9", + "@smithy/smithy-client": "^4.11.2", "@smithy/types": "^4.12.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", @@ -8346,9 +7655,9 @@ } }, "node_modules/@smithy/node-http-handler": { - "version": "4.4.8", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.8.tgz", - "integrity": "sha512-q9u+MSbJVIJ1QmJ4+1u+cERXkrhuILCBDsJUBAW1MPE6sFonbCNaegFuwW9ll8kh5UdyY3jOkoOGlc7BesoLpg==", + "version": "4.4.9", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.9.tgz", + "integrity": "sha512-KX5Wml5mF+luxm1szW4QDz32e3NObgJ4Fyw+irhph4I/2geXwUy4jkIMUs5ZPGflRBeR6BUkC2wqIab4Llgm3w==", "license": "Apache-2.0", "dependencies": { "@smithy/abort-controller": "^4.2.8", @@ -8459,17 +7768,17 @@ } }, "node_modules/@smithy/smithy-client": { - "version": "4.10.9", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.10.9.tgz", - "integrity": "sha512-Je0EvGXVJ0Vrrr2lsubq43JGRIluJ/hX17aN/W/A0WfE+JpoMdI8kwk2t9F0zTX9232sJDGcoH4zZre6m6f/sg==", + "version": "4.11.2", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.11.2.tgz", + "integrity": "sha512-SCkGmFak/xC1n7hKRsUr6wOnBTJ3L22Qd4e8H1fQIuKTAjntwgU8lrdMe7uHdiT2mJAOWA/60qaW9tiMu69n1A==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.20.7", - "@smithy/middleware-endpoint": "^4.4.8", + "@smithy/core": "^3.22.1", + "@smithy/middleware-endpoint": "^4.4.13", "@smithy/middleware-stack": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", - "@smithy/util-stream": "^4.5.10", + "@smithy/util-stream": "^4.5.11", "tslib": "^2.6.2" }, "engines": { @@ -8566,13 +7875,13 @@ } }, "node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.3.23", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.23.tgz", - "integrity": "sha512-mMg+r/qDfjfF/0psMbV4zd7F/i+rpyp7Hjh0Wry7eY15UnzTEId+xmQTGDU8IdZtDfbGQxuWNfgBZKBj+WuYbA==", + "version": "4.3.29", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.29.tgz", + "integrity": "sha512-nIGy3DNRmOjaYaaKcQDzmWsro9uxlaqUOhZDHQed9MW/GmkBZPtnU70Pu1+GT9IBmUXwRdDuiyaeiy9Xtpn3+Q==", "license": "Apache-2.0", "dependencies": { "@smithy/property-provider": "^4.2.8", - "@smithy/smithy-client": "^4.10.9", + "@smithy/smithy-client": "^4.11.2", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, @@ -8581,16 +7890,16 @@ } }, "node_modules/@smithy/util-defaults-mode-node": { - "version": "4.2.26", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.26.tgz", - "integrity": "sha512-EQqe/WkbCinah0h1lMWh9ICl0Ob4lyl20/10WTB35SC9vDQfD8zWsOT+x2FIOXKAoZQ8z/y0EFMoodbcqWJY/w==", + "version": "4.2.32", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.32.tgz", + "integrity": "sha512-7dtFff6pu5fsjqrVve0YMhrnzJtccCWDacNKOkiZjJ++fmjGExmmSu341x+WU6Oc1IccL7lDuaUj7SfrHpWc5Q==", "license": "Apache-2.0", "dependencies": { "@smithy/config-resolver": "^4.4.6", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", - "@smithy/smithy-client": "^4.10.9", + "@smithy/smithy-client": "^4.11.2", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, @@ -8652,13 +7961,13 @@ } }, "node_modules/@smithy/util-stream": { - "version": "4.5.10", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.10.tgz", - "integrity": "sha512-jbqemy51UFSZSp2y0ZmRfckmrzuKww95zT9BYMmuJ8v3altGcqjwoV1tzpOwuHaKrwQrCjIzOib499ymr2f98g==", + "version": "4.5.11", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.11.tgz", + "integrity": "sha512-lKmZ0S/3Qj2OF5H1+VzvDLb6kRxGzZHq6f3rAsoSu5cTLGsn3v3VQBA8czkNNXlLjoFEtVu3OQT2jEeOtOE2CA==", "license": "Apache-2.0", "dependencies": { "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/node-http-handler": "^4.4.8", + "@smithy/node-http-handler": "^4.4.9", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-buffer-from": "^4.2.0", @@ -9316,9 +8625,9 @@ } }, "node_modules/@tanstack/query-core": { - "version": "5.90.12", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.12.tgz", - "integrity": "sha512-T1/8t5DhV/SisWjDnaiU2drl6ySvsHj1bHBCWNXd+/T+Hh1cf6JodyEYMd5sgwm+b/mETT4EV3H+zCVczCU5hg==", + "version": "5.90.20", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.20.tgz", + "integrity": "sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg==", "license": "MIT", "funding": { "type": "github", @@ -9326,9 +8635,9 @@ } }, "node_modules/@tanstack/query-devtools": { - "version": "5.91.1", - "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.91.1.tgz", - "integrity": "sha512-l8bxjk6BMsCaVQH6NzQEE/bEgFy1hAs5qbgXl0xhzezlaQbPk6Mgz9BqEg2vTLPOHD8N4k+w/gdgCbEzecGyNg==", + "version": "5.93.0", + "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.93.0.tgz", + "integrity": "sha512-+kpsx1NQnOFTZsw6HAFCW3HkKg0+2cepGtAWXjiiSOJJ1CtQpt72EE2nyZb+AjAbLRPoeRmPJ8MtQd8r8gsPdg==", "dev": true, "license": "MIT", "funding": { @@ -9337,12 +8646,13 @@ } }, "node_modules/@tanstack/react-query": { - "version": "5.90.12", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.12.tgz", - "integrity": "sha512-graRZspg7EoEaw0a8faiUASCyJrqjKPdqJ9EwuDRUF9mEYJ1YPczI9H+/agJ0mOJkPCJDk0lsz5QTrLZ/jQ2rg==", + "version": "5.90.20", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.20.tgz", + "integrity": "sha512-vXBxa+qeyveVO7OA0jX1z+DeyCA4JKnThKv411jd5SORpBKgkcVnYKCiBgECvADvniBX7tobwBmg01qq9JmMJw==", "license": "MIT", + "peer": true, "dependencies": { - "@tanstack/query-core": "5.90.12" + "@tanstack/query-core": "5.90.20" }, "funding": { "type": "github", @@ -9353,20 +8663,20 @@ } }, "node_modules/@tanstack/react-query-devtools": { - "version": "5.91.1", - "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.91.1.tgz", - "integrity": "sha512-tRnJYwEbH0kAOuToy8Ew7bJw1lX3AjkkgSlf/vzb+NpnqmHPdWM+lA2DSdGQSLi1SU0PDRrrCI1vnZnci96CsQ==", + "version": "5.91.3", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.91.3.tgz", + "integrity": "sha512-nlahjMtd/J1h7IzOOfqeyDh5LNfG0eULwlltPEonYy0QL+nqrBB+nyzJfULV+moL7sZyxc2sHdNJki+vLA9BSA==", "dev": true, "license": "MIT", "dependencies": { - "@tanstack/query-devtools": "5.91.1" + "@tanstack/query-devtools": "5.93.0" }, "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" }, "peerDependencies": { - "@tanstack/react-query": "^5.90.10", + "@tanstack/react-query": "^5.90.20", "react": "^18 || ^19" } }, @@ -9446,6 +8756,7 @@ "integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "@types/node": "*" } @@ -9785,6 +9096,7 @@ "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^5.0.0", @@ -9873,23 +9185,23 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.10.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.2.tgz", - "integrity": "sha512-WOhQTZ4G8xZ1tjJTvKOpyEVSGgOTvJAfDK3FNFgELyaTpzhdgHVHeqW8V+UJvzF5BT+/B54T/1S2K6gd9c7bbA==", + "version": "25.2.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.0.tgz", + "integrity": "sha512-DZ8VwRFUNzuqJ5khrvwMXHmvPe+zGayJhr2CDNiKB1WBE1ST8Djl00D0IC4vvNmHMdj6DlbYRIaFE7WHjlDl5w==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.16.0" } }, "node_modules/@types/nodemailer": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-7.0.4.tgz", - "integrity": "sha512-ee8fxWqOchH+Hv6MDDNNy028kwvVnLplrStm4Zf/3uHWw5zzo8FoYYeffpJtGs2wWysEumMH0ZIdMGMY1eMAow==", + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-7.0.9.tgz", + "integrity": "sha512-vI8oF1M+8JvQhsId0Pc38BdUP2evenIIys7c7p+9OZXSPOH5c1dyINP1jT8xQ2xPuBUXmIC87s+91IZMDjH8Ow==", "dev": true, "license": "MIT", "dependencies": { - "@aws-sdk/client-sesv2": "^3.839.0", "@types/node": "*" } }, @@ -9906,6 +9218,7 @@ "integrity": "sha512-RmhMd/wD+CF8Dfo+cVIy3RR5cl8CyfXQ0tGgW6XBL8L4LM/UTEbNXYRbLwU6w+CgrKBNbrQWt4FUtTfaU5jSYQ==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "@types/node": "*", "pg-protocol": "*", @@ -9927,11 +9240,12 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "19.2.7", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", - "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", + "version": "19.2.10", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.10.tgz", + "integrity": "sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -9942,6 +9256,7 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "devOptional": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -10017,8 +9332,7 @@ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", "license": "MIT", - "optional": true, - "peer": true + "optional": true }, "node_modules/@types/ws": { "version": "8.18.1", @@ -10048,16 +9362,16 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.53.1.tgz", - "integrity": "sha512-cFYYFZ+oQFi6hUnBTbLRXfTJiaQtYE3t4O692agbBl+2Zy+eqSKWtPjhPXJu1G7j4RLjKgeJPDdq3EqOwmX5Ag==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.54.0.tgz", + "integrity": "sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ==", "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.53.1", - "@typescript-eslint/type-utils": "8.53.1", - "@typescript-eslint/utils": "8.53.1", - "@typescript-eslint/visitor-keys": "8.53.1", + "@typescript-eslint/scope-manager": "8.54.0", + "@typescript-eslint/type-utils": "8.54.0", + "@typescript-eslint/utils": "8.54.0", + "@typescript-eslint/visitor-keys": "8.54.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" @@ -10070,7 +9384,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.53.1", + "@typescript-eslint/parser": "^8.54.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } @@ -10085,15 +9399,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.53.1.tgz", - "integrity": "sha512-nm3cvFN9SqZGXjmw5bZ6cGmvJSyJPn0wU9gHAZZHDnZl2wF9PhHv78Xf06E0MaNk4zLVHL8hb2/c32XvyJOLQg==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.54.0.tgz", + "integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==", "license": "MIT", + "peer": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.53.1", - "@typescript-eslint/types": "8.53.1", - "@typescript-eslint/typescript-estree": "8.53.1", - "@typescript-eslint/visitor-keys": "8.53.1", + "@typescript-eslint/scope-manager": "8.54.0", + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/typescript-estree": "8.54.0", + "@typescript-eslint/visitor-keys": "8.54.0", "debug": "^4.4.3" }, "engines": { @@ -10109,13 +9424,13 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.53.1.tgz", - "integrity": "sha512-WYC4FB5Ra0xidsmlPb+1SsnaSKPmS3gsjIARwbEkHkoWloQmuzcfypljaJcR78uyLA1h8sHdWWPHSLDI+MtNog==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.54.0.tgz", + "integrity": "sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g==", "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.53.1", - "@typescript-eslint/types": "^8.53.1", + "@typescript-eslint/tsconfig-utils": "^8.54.0", + "@typescript-eslint/types": "^8.54.0", "debug": "^4.4.3" }, "engines": { @@ -10130,13 +9445,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.53.1.tgz", - "integrity": "sha512-Lu23yw1uJMFY8cUeq7JlrizAgeQvWugNQzJp8C3x8Eo5Jw5Q2ykMdiiTB9vBVOOUBysMzmRRmUfwFrZuI2C4SQ==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.54.0.tgz", + "integrity": "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.53.1", - "@typescript-eslint/visitor-keys": "8.53.1" + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/visitor-keys": "8.54.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -10147,9 +9462,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.53.1.tgz", - "integrity": "sha512-qfvLXS6F6b1y43pnf0pPbXJ+YoXIC7HKg0UGZ27uMIemKMKA6XH2DTxsEDdpdN29D+vHV07x/pnlPNVLhdhWiA==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.54.0.tgz", + "integrity": "sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==", "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -10163,14 +9478,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.53.1.tgz", - "integrity": "sha512-MOrdtNvyhy0rHyv0ENzub1d4wQYKb2NmIqG7qEqPWFW7Mpy2jzFC3pQ2yKDvirZB7jypm5uGjF2Qqs6OIqu47w==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.54.0.tgz", + "integrity": "sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.53.1", - "@typescript-eslint/typescript-estree": "8.53.1", - "@typescript-eslint/utils": "8.53.1", + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/typescript-estree": "8.54.0", + "@typescript-eslint/utils": "8.54.0", "debug": "^4.4.3", "ts-api-utils": "^2.4.0" }, @@ -10187,9 +9502,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.53.1.tgz", - "integrity": "sha512-jr/swrr2aRmUAUjW5/zQHbMaui//vQlsZcJKijZf3M26bnmLj8LyZUpj8/Rd6uzaek06OWsqdofN/Thenm5O8A==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.54.0.tgz", + "integrity": "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==", "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -10200,15 +9515,15 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.53.1.tgz", - "integrity": "sha512-RGlVipGhQAG4GxV1s34O91cxQ/vWiHJTDHbXRr0li2q/BGg3RR/7NM8QDWgkEgrwQYCvmJV9ichIwyoKCQ+DTg==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.54.0.tgz", + "integrity": "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==", "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.53.1", - "@typescript-eslint/tsconfig-utils": "8.53.1", - "@typescript-eslint/types": "8.53.1", - "@typescript-eslint/visitor-keys": "8.53.1", + "@typescript-eslint/project-service": "8.54.0", + "@typescript-eslint/tsconfig-utils": "8.54.0", + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/visitor-keys": "8.54.0", "debug": "^4.4.3", "minimatch": "^9.0.5", "semver": "^7.7.3", @@ -10251,15 +9566,15 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.53.1.tgz", - "integrity": "sha512-c4bMvGVWW4hv6JmDUEG7fSYlWOl3II2I4ylt0NM+seinYQlZMQIaKaXIIVJWt9Ofh6whrpM+EdDQXKXjNovvrg==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.54.0.tgz", + "integrity": "sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==", "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.53.1", - "@typescript-eslint/types": "8.53.1", - "@typescript-eslint/typescript-estree": "8.53.1" + "@typescript-eslint/scope-manager": "8.54.0", + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/typescript-estree": "8.54.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -10274,12 +9589,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.53.1.tgz", - "integrity": "sha512-oy+wV7xDKFPRyNggmXuZQSBzvoLnpmJs+GhzRhPjrxl2b/jIlyjVokzm47CZCDUdXKr2zd7ZLodPfOBpOPyPlg==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.54.0.tgz", + "integrity": "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.53.1", + "@typescript-eslint/types": "8.54.0", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -10540,6 +9855,17 @@ "win32" ] }, + "node_modules/@zone-eu/mailsplit": { + "version": "5.4.8", + "resolved": "https://registry.npmjs.org/@zone-eu/mailsplit/-/mailsplit-5.4.8.tgz", + "integrity": "sha512-eEyACj4JZ7sjzRvy26QhLgKEMWwQbsw1+QZnlLX+/gihcNH07lVPOcnwf5U6UAL7gkc//J3jVd76o/WS+taUiA==", + "license": "(MIT OR EUPL-1.1+)", + "dependencies": { + "libbase64": "1.3.0", + "libmime": "5.3.7", + "libqp": "2.1.1" + } + }, "node_modules/accepts": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", @@ -10558,6 +9884,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -10963,9 +10290,9 @@ } }, "node_modules/axios": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", - "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.4.tgz", + "integrity": "sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", @@ -10988,6 +10315,7 @@ "integrity": "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/types": "^7.26.0" } @@ -11043,6 +10371,7 @@ "integrity": "sha512-Ba0KR+Fzxh2jDRhdg6TSH0SJGzb8C0aBY4hR8w8madIdIzzC6Y1+kx5qR6eS1Z+Gy20h6ZU28aeyg0z1VIrShQ==", "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "bindings": "^1.5.0", "prebuild-install": "^7.1.1" @@ -11168,6 +10497,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -11751,9 +11081,9 @@ } }, "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", "license": "MIT", "dependencies": { "object-assign": "^4", @@ -11761,6 +11091,10 @@ }, "engines": { "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/cross-spawn": { @@ -12136,6 +11470,7 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", + "peer": true, "engines": { "node": ">=12" } @@ -12565,7 +11900,6 @@ "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz", "integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==", "license": "(MPL-2.0 OR Apache-2.0)", - "peer": true, "optionalDependencies": { "@types/trusted-types": "^2.0.7" } @@ -13319,6 +12653,15 @@ "node": ">= 0.8" } }, + "node_modules/encoding-japanese": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/encoding-japanese/-/encoding-japanese-2.2.0.tgz", + "integrity": "sha512-EuJWwlHPZ1LbADuKTClvHtwbaFn4rOD+dRAbWysqEOXRc2Uui0hJInNJrsdH0c+OhJA4nrCBdSkW4DD5YxAo6A==", + "license": "MIT", + "engines": { + "node": ">=8.10.0" + } + }, "node_modules/end-of-stream": { "version": "1.4.5", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", @@ -13671,6 +13014,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -13767,6 +13111,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -13822,12 +13167,12 @@ } }, "node_modules/eslint-config-next": { - "version": "16.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.1.0.tgz", - "integrity": "sha512-RlPb8E2uO/Ix/w3kizxz6+6ogw99WqtNzTG0ArRZ5NEkIYcsfRb8U0j7aTG7NjRvcrsak5QtUSuxGNN2UcA58g==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.1.6.tgz", + "integrity": "sha512-vKq40io2B0XtkkNDYyleATwblNt8xuh3FWp8SpSz3pt7P01OkBFlKsJZ2mWt5WsCySlDQLckb1zMY9yE9Qy0LA==", "license": "MIT", "dependencies": { - "@next/eslint-plugin-next": "16.1.0", + "@next/eslint-plugin-next": "16.1.6", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-import": "^2.32.0", @@ -14249,6 +13594,7 @@ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", + "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", @@ -15109,6 +14455,15 @@ "node": ">= 0.4" } }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, "node_modules/helmet": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz", @@ -15218,6 +14573,21 @@ "node": ">=0.10.0" } }, + "node_modules/icu-minify": { + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/icu-minify/-/icu-minify-4.8.2.tgz", + "integrity": "sha512-LHBQV+skKkjZSPd590pZ7ZAHftUgda3eFjeuNwA8/15L8T8loCNBktKQyTlkodAU86KovFXeg/9WntlAo5wA5A==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/amannn" + } + ], + "license": "MIT", + "dependencies": { + "@formatjs/icu-messageformat-parser": "^3.4.0" + } + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -15318,15 +14688,15 @@ } }, "node_modules/intl-messageformat": { - "version": "10.7.18", - "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.7.18.tgz", - "integrity": "sha512-m3Ofv/X/tV8Y3tHXLohcuVuhWKo7BBq62cqY15etqmLxg2DZ34AGGgQDeR+SCta2+zICb1NX83af0GJmbQ1++g==", + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-11.1.2.tgz", + "integrity": "sha512-ucSrQmZGAxfiBHfBRXW/k7UC8MaGFlEj4Ry1tKiDcmgwQm1y3EDl40u+4VNHYomxJQMJi9NEI3riDRlth96jKg==", "license": "BSD-3-Clause", "dependencies": { - "@formatjs/ecma402-abstract": "2.3.6", - "@formatjs/fast-memoize": "2.2.7", - "@formatjs/icu-messageformat-parser": "2.11.4", - "tslib": "^2.8.0" + "@formatjs/ecma402-abstract": "3.1.1", + "@formatjs/fast-memoize": "3.1.0", + "@formatjs/icu-messageformat-parser": "3.5.1", + "tslib": "^2.8.1" } }, "node_modules/ioredis": { @@ -16059,6 +15429,30 @@ "node": ">= 0.8.0" } }, + "node_modules/libbase64": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/libbase64/-/libbase64-1.3.0.tgz", + "integrity": "sha512-GgOXd0Eo6phYgh0DJtjQ2tO8dc0IVINtZJeARPeiIJqge+HdsWSuaDTe8ztQ7j/cONByDZ3zeB325AHiv5O0dg==", + "license": "MIT" + }, + "node_modules/libmime": { + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/libmime/-/libmime-5.3.7.tgz", + "integrity": "sha512-FlDb3Wtha8P01kTL3P9M+ZDNDWPKPmKHWaU/cG/lg5pfuAwdflVpZE+wm9m7pKmC5ww6s+zTxBKS1p6yl3KpSw==", + "license": "MIT", + "dependencies": { + "encoding-japanese": "2.2.0", + "iconv-lite": "0.6.3", + "libbase64": "1.3.0", + "libqp": "2.1.1" + } + }, + "node_modules/libqp": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/libqp/-/libqp-2.1.1.tgz", + "integrity": "sha512-0Wd+GPz1O134cP62YU2GTOPNA7Qgl09XwCqM5zpBv87ERCXdfDtyKXvV7c9U22yWJh44QZqBocFnXN11K96qow==", + "license": "MIT" + }, "node_modules/lightningcss": { "version": "1.30.2", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", @@ -16320,6 +15714,15 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -16440,9 +15843,9 @@ } }, "node_modules/lucide-react": { - "version": "0.562.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.562.0.tgz", - "integrity": "sha512-82hOAu7y0dbVuFfmO4bYF1XEwYk/mEbM5E+b1jgci/udUBEE/R7LF5Ip0CCEmXe8AybRM8L+04eP+LGZeDvkiw==", + "version": "0.563.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.563.0.tgz", + "integrity": "sha512-8dXPB2GI4dI8jV4MgUDGBeLdGk8ekfqVZ0BdLcrRzocGgG75ltNEmWS+gE7uokKF/0oSUuczNDT+g9hFJ23FkA==", "license": "ISC", "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" @@ -16458,6 +15861,49 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/mailparser": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/mailparser/-/mailparser-3.9.1.tgz", + "integrity": "sha512-6vHZcco3fWsDMkf4Vz9iAfxvwrKNGbHx0dV1RKVphQ/zaNY34Buc7D37LSa09jeSeybWzYcTPjhiZFxzVRJedA==", + "license": "MIT", + "dependencies": { + "@zone-eu/mailsplit": "5.4.8", + "encoding-japanese": "2.2.0", + "he": "1.2.0", + "html-to-text": "9.0.5", + "iconv-lite": "0.7.0", + "libmime": "5.3.7", + "linkify-it": "5.0.0", + "nodemailer": "7.0.11", + "punycode.js": "2.3.1", + "tlds": "1.261.0" + } + }, + "node_modules/mailparser/node_modules/iconv-lite": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/mailparser/node_modules/nodemailer": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.11.tgz", + "integrity": "sha512-gnXhNRE0FNhD7wPSCGhdNh46Hs6nm+uTyg+Kq0cZukNQiYdnCsoQjodNP9BQVG9XrcK/v6/MgpAPBUFyzh9pvw==", + "license": "MIT-0", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/marked": { "version": "15.0.12", "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz", @@ -16480,13 +15926,13 @@ } }, "node_modules/maxmind": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/maxmind/-/maxmind-5.0.1.tgz", - "integrity": "sha512-hYxQxvHkBUlyF34f7IlQOb60rytezCi2oZ8H/BtZpcoodXTlcK1eLgf7kY2TofHqBC3o+Hqtvde9kS72gFQSDw==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/maxmind/-/maxmind-5.0.5.tgz", + "integrity": "sha512-1lcH2kMjbBpCFhuHaMU32vz8CuOsKttRcWMQyXvtlklopCzN7NNHSVR/h9RYa8JPuFTGmkn2vYARm+7cIGuqDw==", "license": "MIT", "dependencies": { - "mmdb-lib": "3.0.1", - "tiny-lru": "11.4.5" + "mmdb-lib": "3.0.2", + "tiny-lru": "11.4.7" }, "engines": { "node": ">=12", @@ -16684,9 +16130,9 @@ "license": "MIT" }, "node_modules/mmdb-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mmdb-lib/-/mmdb-lib-3.0.1.tgz", - "integrity": "sha512-dyAyMR+cRykZd1mw5altC9f4vKpCsuywPwo8l/L5fKqDay2zmqT0mF/BvUoXnQiqGn+nceO914rkPKJoyFnGxA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mmdb-lib/-/mmdb-lib-3.0.2.tgz", + "integrity": "sha512-7e87vk0DdWT647wjcfEtWeMtjm+zVGqNohN/aeIymbUfjHQ2T4Sx5kM+1irVDBSloNC3CkGKxswdMoo8yhqTDg==", "license": "MIT", "engines": { "node": ">=10", @@ -16707,7 +16153,6 @@ "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz", "integrity": "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==", "license": "MIT", - "peer": true, "dependencies": { "dompurify": "3.2.7", "marked": "14.0.0" @@ -16718,7 +16163,6 @@ "resolved": "https://registry.npmjs.org/marked/-/marked-14.0.0.tgz", "integrity": "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==", "license": "MIT", - "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -16805,6 +16249,7 @@ "resolved": "https://registry.npmjs.org/next/-/next-15.5.9.tgz", "integrity": "sha512-agNLK89seZEtC5zUHwtut0+tNrc0Xw4FT/Dg+B/VLEo9pAcS9rtTKpek3V6kVcVwsB2YlqMaHdfZL4eLEVYuCg==", "license": "MIT", + "peer": true, "dependencies": { "@next/env": "15.5.9", "@swc/helpers": "0.5.15", @@ -16853,9 +16298,9 @@ } }, "node_modules/next-intl": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/next-intl/-/next-intl-4.7.0.tgz", - "integrity": "sha512-gvROzcNr/HM0jTzQlKWQxUNk8jrZ0bREz+bht3wNbv+uzlZ5Kn3J+m+viosub18QJ72S08UJnVK50PXWcUvwpQ==", + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/next-intl/-/next-intl-4.8.2.tgz", + "integrity": "sha512-GuuwyvyEI49/oehQbBXEoY8KSIYCzmfMLhmIwhMXTb+yeBmly1PnJcpgph3KczQ+HTJMXwXCmkizgtT8jBMf3A==", "funding": [ { "type": "individual", @@ -16867,10 +16312,11 @@ "@formatjs/intl-localematcher": "^0.5.4", "@parcel/watcher": "^2.4.1", "@swc/core": "^1.15.2", + "icu-minify": "^4.8.2", "negotiator": "^1.0.0", - "next-intl-swc-plugin-extractor": "^4.7.0", + "next-intl-swc-plugin-extractor": "^4.8.2", "po-parser": "^2.1.1", - "use-intl": "^4.7.0" + "use-intl": "^4.8.2" }, "peerDependencies": { "next": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0", @@ -16884,9 +16330,9 @@ } }, "node_modules/next-intl-swc-plugin-extractor": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/next-intl-swc-plugin-extractor/-/next-intl-swc-plugin-extractor-4.7.0.tgz", - "integrity": "sha512-iAqflu2FWdQMWhwB0B2z52X7LmEpvnMNJXqVERZQ7bK5p9iqQLu70ur6Ka6NfiXLxfb+AeAkUX5qIciQOg+87A==", + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/next-intl-swc-plugin-extractor/-/next-intl-swc-plugin-extractor-4.8.2.tgz", + "integrity": "sha512-sHDs36L1VZmFHj3tPHsD+KZJtnsRudHlNvT0ieIe3iFVn5OpGLTxW3d/Zc/2LXSj5GpGuR6wQeikbhFjU9tMQQ==", "license": "MIT" }, "node_modules/next-themes": { @@ -17029,9 +16475,9 @@ "license": "MIT" }, "node_modules/nodemailer": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.11.tgz", - "integrity": "sha512-gnXhNRE0FNhD7wPSCGhdNh46Hs6nm+uTyg+Kq0cZukNQiYdnCsoQjodNP9BQVG9XrcK/v6/MgpAPBUFyzh9pvw==", + "version": "7.0.13", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.13.tgz", + "integrity": "sha512-PNDFSJdP+KFgdsG3ZzMXCgquO7I6McjY2vlqILjtJd0hy8wEvtugS9xKRF2NWlPNGxvLCXlTNIae4serI7dinw==", "license": "MIT-0", "engines": { "node": ">=6.0.0" @@ -17048,9 +16494,9 @@ } }, "node_modules/npm": { - "version": "11.7.0", - "resolved": "https://registry.npmjs.org/npm/-/npm-11.7.0.tgz", - "integrity": "sha512-wiCZpv/41bIobCoJ31NStIWKfAxxYyD1iYnWCtiyns8s5v3+l8y0HCP/sScuH6B5+GhIfda4HQKiqeGZwJWhFw==", + "version": "11.8.0", + "resolved": "https://registry.npmjs.org/npm/-/npm-11.8.0.tgz", + "integrity": "sha512-n19sJeW+RGKdkHo8SCc5xhSwkKhQUFfZaFzSc+EsYXLjSqIV0tl72aDYQVuzVvfrbysGwdaQsNLNy58J10EBSQ==", "bundleDependencies": [ "@isaacs/string-locale-compare", "@npmcli/arborist", @@ -17129,8 +16575,8 @@ ], "dependencies": { "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/arborist": "^9.1.9", - "@npmcli/config": "^10.4.5", + "@npmcli/arborist": "^9.1.10", + "@npmcli/config": "^10.5.0", "@npmcli/fs": "^5.0.0", "@npmcli/map-workspaces": "^5.0.3", "@npmcli/metavuln-calculator": "^9.0.3", @@ -17138,7 +16584,7 @@ "@npmcli/promise-spawn": "^9.0.1", "@npmcli/redact": "^4.0.0", "@npmcli/run-script": "^10.0.3", - "@sigstore/tuf": "^4.0.0", + "@sigstore/tuf": "^4.0.1", "abbrev": "^4.0.0", "archy": "~1.0.0", "cacache": "^20.0.3", @@ -17155,11 +16601,11 @@ "is-cidr": "^6.0.1", "json-parse-even-better-errors": "^5.0.0", "libnpmaccess": "^10.0.3", - "libnpmdiff": "^8.0.12", - "libnpmexec": "^10.1.11", - "libnpmfund": "^7.0.12", + "libnpmdiff": "^8.0.13", + "libnpmexec": "^10.1.12", + "libnpmfund": "^7.0.13", "libnpmorg": "^8.0.1", - "libnpmpack": "^9.0.12", + "libnpmpack": "^9.0.13", "libnpmpublish": "^11.1.3", "libnpmsearch": "^9.0.1", "libnpmteam": "^8.0.2", @@ -17188,11 +16634,11 @@ "spdx-expression-parse": "^4.0.0", "ssri": "^13.0.0", "supports-color": "^10.2.2", - "tar": "^7.5.2", + "tar": "^7.5.4", "text-table": "~0.2.0", "tiny-relative-date": "^2.0.2", "treeverse": "^3.0.0", - "validate-npm-package-name": "^7.0.0", + "validate-npm-package-name": "^7.0.2", "which": "^6.0.0" }, "bin": { @@ -17267,7 +16713,7 @@ } }, "node_modules/npm/node_modules/@npmcli/arborist": { - "version": "9.1.9", + "version": "9.1.10", "inBundle": true, "license": "ISC", "dependencies": { @@ -17284,7 +16730,7 @@ "@npmcli/run-script": "^10.0.0", "bin-links": "^6.0.0", "cacache": "^20.0.1", - "common-ancestor-path": "^1.0.1", + "common-ancestor-path": "^2.0.0", "hosted-git-info": "^9.0.0", "json-stringify-nice": "^1.1.4", "lru-cache": "^11.2.1", @@ -17313,7 +16759,7 @@ } }, "node_modules/npm/node_modules/@npmcli/config": { - "version": "10.4.5", + "version": "10.5.0", "inBundle": true, "license": "ISC", "dependencies": { @@ -17494,7 +16940,7 @@ } }, "node_modules/npm/node_modules/@sigstore/core": { - "version": "3.0.0", + "version": "3.1.0", "inBundle": true, "license": "Apache-2.0", "engines": { @@ -17510,48 +16956,40 @@ } }, "node_modules/npm/node_modules/@sigstore/sign": { - "version": "4.0.1", + "version": "4.1.0", "inBundle": true, "license": "Apache-2.0", "dependencies": { "@sigstore/bundle": "^4.0.0", - "@sigstore/core": "^3.0.0", + "@sigstore/core": "^3.1.0", "@sigstore/protobuf-specs": "^0.5.0", - "make-fetch-happen": "^15.0.2", - "proc-log": "^5.0.0", + "make-fetch-happen": "^15.0.3", + "proc-log": "^6.1.0", "promise-retry": "^2.0.1" }, "engines": { "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/@sigstore/sign/node_modules/proc-log": { - "version": "5.0.0", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, "node_modules/npm/node_modules/@sigstore/tuf": { - "version": "4.0.0", + "version": "4.0.1", "inBundle": true, "license": "Apache-2.0", "dependencies": { "@sigstore/protobuf-specs": "^0.5.0", - "tuf-js": "^4.0.0" + "tuf-js": "^4.1.0" }, "engines": { "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@sigstore/verify": { - "version": "3.0.0", + "version": "3.1.0", "inBundle": true, "license": "Apache-2.0", "dependencies": { "@sigstore/bundle": "^4.0.0", - "@sigstore/core": "^3.0.0", + "@sigstore/core": "^3.1.0", "@sigstore/protobuf-specs": "^0.5.0" }, "engines": { @@ -17567,31 +17005,17 @@ } }, "node_modules/npm/node_modules/@tufjs/models": { - "version": "4.0.0", + "version": "4.1.0", "inBundle": true, "license": "MIT", "dependencies": { "@tufjs/canonical-json": "2.0.0", - "minimatch": "^9.0.5" + "minimatch": "^10.1.1" }, "engines": { "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/@tufjs/models/node_modules/minimatch": { - "version": "9.0.5", - "inBundle": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/npm/node_modules/abbrev": { "version": "4.0.0", "inBundle": true, @@ -17626,11 +17050,6 @@ "inBundle": true, "license": "MIT" }, - "node_modules/npm/node_modules/balanced-match": { - "version": "1.0.2", - "inBundle": true, - "license": "MIT" - }, "node_modules/npm/node_modules/bin-links": { "version": "6.0.0", "inBundle": true, @@ -17657,14 +17076,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm/node_modules/brace-expansion": { - "version": "2.0.2", - "inBundle": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/npm/node_modules/cacache": { "version": "20.0.3", "inBundle": true, @@ -17751,9 +17162,12 @@ } }, "node_modules/npm/node_modules/common-ancestor-path": { - "version": "1.0.1", + "version": "2.0.0", "inBundle": true, - "license": "ISC" + "license": "BlueOak-1.0.0", + "engines": { + "node": ">= 18" + } }, "node_modules/npm/node_modules/cssesc": { "version": "3.0.0", @@ -17783,7 +17197,7 @@ } }, "node_modules/npm/node_modules/diff": { - "version": "8.0.2", + "version": "8.0.3", "inBundle": true, "license": "BSD-3-Clause", "engines": { @@ -17959,7 +17373,7 @@ } }, "node_modules/npm/node_modules/ip-address": { - "version": "10.0.1", + "version": "10.1.0", "inBundle": true, "license": "MIT", "engines": { @@ -18051,11 +17465,11 @@ } }, "node_modules/npm/node_modules/libnpmdiff": { - "version": "8.0.12", + "version": "8.0.13", "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^9.1.9", + "@npmcli/arborist": "^9.1.10", "@npmcli/installed-package-contents": "^4.0.0", "binary-extensions": "^3.0.0", "diff": "^8.0.2", @@ -18069,11 +17483,11 @@ } }, "node_modules/npm/node_modules/libnpmexec": { - "version": "10.1.11", + "version": "10.1.12", "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^9.1.9", + "@npmcli/arborist": "^9.1.10", "@npmcli/package-json": "^7.0.0", "@npmcli/run-script": "^10.0.0", "ci-info": "^4.0.0", @@ -18091,11 +17505,11 @@ } }, "node_modules/npm/node_modules/libnpmfund": { - "version": "7.0.12", + "version": "7.0.13", "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^9.1.9" + "@npmcli/arborist": "^9.1.10" }, "engines": { "node": "^20.17.0 || >=22.9.0" @@ -18114,11 +17528,11 @@ } }, "node_modules/npm/node_modules/libnpmpack": { - "version": "9.0.12", + "version": "9.0.13", "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^9.1.9", + "@npmcli/arborist": "^9.1.10", "@npmcli/run-script": "^10.0.0", "npm-package-arg": "^13.0.0", "pacote": "^21.0.2" @@ -18184,9 +17598,9 @@ } }, "node_modules/npm/node_modules/lru-cache": { - "version": "11.2.2", + "version": "11.2.4", "inBundle": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { "node": "20 || >=22" } @@ -18567,7 +17981,7 @@ } }, "node_modules/npm/node_modules/path-scurry": { - "version": "2.0.0", + "version": "2.0.1", "inBundle": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -18582,7 +17996,7 @@ } }, "node_modules/npm/node_modules/postcss-selector-parser": { - "version": "7.1.0", + "version": "7.1.1", "inBundle": true, "license": "MIT", "dependencies": { @@ -18711,16 +18125,16 @@ } }, "node_modules/npm/node_modules/sigstore": { - "version": "4.0.0", + "version": "4.1.0", "inBundle": true, "license": "Apache-2.0", "dependencies": { "@sigstore/bundle": "^4.0.0", - "@sigstore/core": "^3.0.0", + "@sigstore/core": "^3.1.0", "@sigstore/protobuf-specs": "^0.5.0", - "@sigstore/sign": "^4.0.0", - "@sigstore/tuf": "^4.0.0", - "@sigstore/verify": "^3.0.0" + "@sigstore/sign": "^4.1.0", + "@sigstore/tuf": "^4.0.1", + "@sigstore/verify": "^3.1.0" }, "engines": { "node": "^20.17.0 || >=22.9.0" @@ -18845,7 +18259,7 @@ } }, "node_modules/npm/node_modules/tar": { - "version": "7.5.2", + "version": "7.5.4", "inBundle": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -18912,6 +18326,7 @@ "version": "4.0.3", "inBundle": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -18928,13 +18343,13 @@ } }, "node_modules/npm/node_modules/tuf-js": { - "version": "4.0.0", + "version": "4.1.0", "inBundle": true, "license": "MIT", "dependencies": { - "@tufjs/models": "4.0.0", - "debug": "^4.4.1", - "make-fetch-happen": "^15.0.0" + "@tufjs/models": "4.1.0", + "debug": "^4.4.3", + "make-fetch-happen": "^15.0.1" }, "engines": { "node": "^20.17.0 || >=22.9.0" @@ -18986,7 +18401,7 @@ } }, "node_modules/npm/node_modules/validate-npm-package-name": { - "version": "7.0.0", + "version": "7.0.2", "inBundle": true, "license": "ISC", "engines": { @@ -19740,12 +19155,13 @@ } }, "node_modules/pg": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.17.1.tgz", - "integrity": "sha512-EIR+jXdYNSMOrpRp7g6WgQr7SaZNZfS7IzZIO0oTNEeibq956JxeD15t3Jk3zZH0KH8DmOIx38qJfQenoE8bXQ==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.18.0.tgz", + "integrity": "sha512-xqrUDL1b9MbkydY/s+VZ6v+xiMUmOUk7SS9d/1kpyQxoJ6U9AO1oIJyUWVZojbfe5Cc/oluutcgFG4L9RDP1iQ==", "license": "MIT", + "peer": true, "dependencies": { - "pg-connection-string": "^2.10.0", + "pg-connection-string": "^2.11.0", "pg-pool": "^3.11.0", "pg-protocol": "^1.11.0", "pg-types": "2.2.0", @@ -19774,9 +19190,9 @@ "optional": true }, "node_modules/pg-connection-string": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.10.0.tgz", - "integrity": "sha512-ur/eoPKzDx2IjPaYyXS6Y8NSblxM7X64deV2ObV57vhjsWiwLvUD6meukAzogiOsu60GO8m/3Cb6FdJsWNjwXg==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.11.0.tgz", + "integrity": "sha512-kecgoJwhOpxYU21rZjULrmrBJ698U2RxXofKVzOn5UDj61BPj/qMb7diYUR1nLScCDbrztQFl1TaQZT0t1EtzQ==", "license": "MIT" }, "node_modules/pg-int8": { @@ -19955,12 +19371,12 @@ } }, "node_modules/posthog-node": { - "version": "5.23.0", - "resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-5.23.0.tgz", - "integrity": "sha512-MxcBgY9l2qqLpUJqeIT/zm/s6VBp2zTWvamvkGwyuSP/vcg375+nQcgDtsg5PgEXPP/sFhVnr9Ae48ykd5wQ7A==", + "version": "5.24.7", + "resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-5.24.7.tgz", + "integrity": "sha512-IJ0Zj+v+eg/JQMZ75n0Hcp4NzuQzWcZjqFjcUQs6RhW2l5FiQIq09sKJMleXX33hYxD6sfjFsDTqugJlgeAohg==", "license": "MIT", "dependencies": { - "@posthog/core": "1.11.0" + "@posthog/core": "1.17.0" }, "engines": { "node": "^20.20.0 || >=22.22.0" @@ -20002,9 +19418,9 @@ } }, "node_modules/prettier": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.0.tgz", - "integrity": "sha512-yEPsovQfpxYfgWNhCfECjG5AQaO+K3dp6XERmOepyPDVqcJm+bjyCVO3pmU+nAPe0N5dDvekfGezt/EIiRe1TA==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" @@ -20088,6 +19504,15 @@ "node": ">=6" } }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/pvtsutils": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz", @@ -20225,10 +19650,11 @@ } }, "node_modules/react": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", - "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -20255,15 +19681,16 @@ } }, "node_modules/react-dom": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", - "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^19.2.3" + "react": "^19.2.4" } }, "node_modules/react-easy-sort": { @@ -21075,6 +20502,7 @@ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.71.1.tgz", "integrity": "sha512-9SUJKCGKo8HUSsCO+y0CtqkqI5nNuaDqTxyqPsZPqIwudpj4rCrAz/jZV+jn57bx5gtZKOh3neQu94DXMc+w5w==", "license": "MIT", + "peer": true, "engines": { "node": ">=18.0.0" }, @@ -21367,11 +20795,12 @@ } }, "node_modules/resend": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/resend/-/resend-6.8.0.tgz", - "integrity": "sha512-fDOXGqafQfQXl8nXe93wr93pus8tW7YPpowenE3SmG7dJJf0hH3xUWm3xqacnPvhqjCQTJH9xETg07rmUeSuqQ==", + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/resend/-/resend-6.9.1.tgz", + "integrity": "sha512-jFY3qPP2cith1npRXvS7PVdnhbR1CcuzHg65ty5Elv55GKiXhe+nItXuzzoOlKeYJez1iJAo2+8f6ae8sCj0iA==", "license": "MIT", "dependencies": { + "mailparser": "3.9.1", "svix": "1.84.1" }, "engines": { @@ -22425,13 +21854,10 @@ } }, "node_modules/stripe": { - "version": "20.2.0", - "resolved": "https://registry.npmjs.org/stripe/-/stripe-20.2.0.tgz", - "integrity": "sha512-m8niTfdm3nPP/yQswRWMwQxqEUcTtB3RTJQ9oo6NINDzgi7aPOadsH/fPXIIfL1Sc5+lqQFKSk7WiO6CXmvaeA==", + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/stripe/-/stripe-20.3.0.tgz", + "integrity": "sha512-DYzcmV1MfYhycr1GwjCjeQVYk9Gu8dpxyTlu7qeDCsuguug7oUTxPsUQuZeSf/OPzK7pofqobvOKVqAwlpgf/Q==", "license": "MIT", - "dependencies": { - "qs": "^6.14.1" - }, "engines": { "node": ">=16" }, @@ -22445,9 +21871,9 @@ } }, "node_modules/strnum": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", - "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.2.tgz", + "integrity": "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ==", "funding": [ { "type": "github", @@ -22600,7 +22026,8 @@ "version": "4.1.18", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/tapable": { "version": "2.3.0", @@ -22657,9 +22084,9 @@ "license": "MIT" }, "node_modules/tiny-lru": { - "version": "11.4.5", - "resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-11.4.5.tgz", - "integrity": "sha512-hkcz3FjNJfKXjV4mjQ1OrXSLAehg8Hw+cEZclOVT+5c/cWQWImQ9wolzTjth+dmmDe++p3bme3fTxz6Q4Etsqw==", + "version": "11.4.7", + "resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-11.4.7.tgz", + "integrity": "sha512-w/Te7uMUVeH0CR8vZIjr+XiN41V+30lkDdK+NRIDCUYKKuL9VcmaUEmaPISuwGhLlrTGh5yu18lENtR9axSxYw==", "license": "BSD-3-Clause", "engines": { "node": ">=12" @@ -22691,6 +22118,15 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/tlds": { + "version": "1.261.0", + "resolved": "https://registry.npmjs.org/tlds/-/tlds-1.261.0.tgz", + "integrity": "sha512-QXqwfEl9ddlGBaRFXIvNKK6OhipSiLXuRuLJX5DErz0o0Q0rYxulWLdFryTkV5PkdZct5iMInwYEGe/eR++1AA==", + "license": "MIT", + "bin": { + "tlds": "bin.js" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -23068,6 +22504,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -23077,15 +22514,15 @@ } }, "node_modules/typescript-eslint": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.53.1.tgz", - "integrity": "sha512-gB+EVQfP5RDElh9ittfXlhZJdjSU4jUSTyE2+ia8CYyNvet4ElfaLlAIqDvQV9JPknKx0jQH1racTYe/4LaLSg==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.54.0.tgz", + "integrity": "sha512-CKsJ+g53QpsNPqbzUsfKVgd3Lny4yKZ1pP4qN3jdMOg/sisIDLGyDMezycquXLE5JsEU0wp3dGNdzig0/fmSVQ==", "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.53.1", - "@typescript-eslint/parser": "8.53.1", - "@typescript-eslint/typescript-estree": "8.53.1", - "@typescript-eslint/utils": "8.53.1" + "@typescript-eslint/eslint-plugin": "8.54.0", + "@typescript-eslint/parser": "8.54.0", + "@typescript-eslint/typescript-estree": "8.54.0", + "@typescript-eslint/utils": "8.54.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -23099,6 +22536,12 @@ "typescript": ">=4.8.4 <6.0.0" } }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "license": "MIT" + }, "node_modules/uint8array-extras": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", @@ -23241,14 +22684,21 @@ } }, "node_modules/use-intl": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/use-intl/-/use-intl-4.7.0.tgz", - "integrity": "sha512-jyd8nSErVRRsSlUa+SDobKHo9IiWs5fjcPl9VBUnzUyEQpVM5mwJCgw8eUiylhvBpLQzUGox1KN0XlRivSID9A==", + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/use-intl/-/use-intl-4.8.2.tgz", + "integrity": "sha512-3VNXZgDnPFqhIYosQ9W1Hc6K5q+ZelMfawNbexdwL/dY7BTHbceLUBX5Eeex9lgogxTp0pf1SjHuhYNAjr9H3g==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/amannn" + } + ], "license": "MIT", "dependencies": { - "@formatjs/fast-memoize": "^2.2.0", + "@formatjs/fast-memoize": "^3.1.0", "@schummar/icu-type-parser": "1.21.5", - "intl-messageformat": "^10.5.14" + "icu-minify": "^4.8.2", + "intl-messageformat": "^11.1.0" }, "peerDependencies": { "react": "^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0" @@ -23476,6 +22926,7 @@ "resolved": "https://registry.npmjs.org/winston/-/winston-3.19.0.tgz", "integrity": "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==", "license": "MIT", + "peer": true, "dependencies": { "@colors/colors": "^1.6.0", "@dabh/diagnostics": "^2.0.8", @@ -23779,10 +23230,11 @@ } }, "node_modules/zod": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.5.tgz", - "integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/package.json b/package.json index 25d94c4d..38182620 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ }, "dependencies": { "@asteasolutions/zod-to-openapi": "8.4.0", - "@aws-sdk/client-s3": "3.971.0", + "@aws-sdk/client-s3": "3.980.0", "@faker-js/faker": "10.2.0", "@headlessui/react": "2.2.9", "@hookform/resolvers": "5.2.2", @@ -60,16 +60,16 @@ "@radix-ui/react-tabs": "1.1.13", "@radix-ui/react-toast": "1.2.15", "@radix-ui/react-tooltip": "1.2.8", - "@react-email/components": "1.0.2", - "@react-email/render": "2.0.0", - "@react-email/tailwind": "2.0.2", + "@react-email/components": "1.0.6", + "@react-email/render": "2.0.4", + "@react-email/tailwind": "2.0.3", "@simplewebauthn/browser": "13.2.2", "@simplewebauthn/server": "13.2.2", "@tailwindcss/forms": "0.5.11", - "@tanstack/react-query": "5.90.12", + "@tanstack/react-query": "5.90.20", "@tanstack/react-table": "8.21.3", "arctic": "3.7.0", - "axios": "1.13.2", + "axios": "1.13.4", "better-sqlite3": "11.9.1", "canvas-confetti": "1.9.4", "class-variance-authority": "0.7.1", @@ -78,13 +78,13 @@ "cookie": "1.1.1", "cookie-parser": "1.4.7", "cookies": "0.9.1", - "cors": "2.8.5", + "cors": "2.8.6", "crypto-js": "4.2.0", "d3": "7.9.0", "date-fns": "4.1.0", "drizzle-orm": "0.45.1", "eslint": "9.39.2", - "eslint-config-next": "16.1.0", + "eslint-config-next": "16.1.6", "express": "5.2.1", "express-rate-limit": "8.2.1", "glob": "13.0.0", @@ -96,34 +96,34 @@ "jmespath": "0.16.0", "js-yaml": "4.1.1", "jsonwebtoken": "9.0.3", - "lucide-react": "0.562.0", - "maxmind": "5.0.1", + "lucide-react": "0.563.0", + "maxmind": "5.0.5", "moment": "2.30.1", "next": "15.5.9", - "next-intl": "4.7.0", + "next-intl": "4.8.2", "next-themes": "0.4.6", "nextjs-toploader": "3.9.17", "node-cache": "5.1.2", "node-fetch": "3.3.2", - "nodemailer": "7.0.11", - "npm": "11.7.0", + "nodemailer": "7.0.13", + "npm": "11.8.0", "nprogress": "0.2.0", "oslo": "1.2.1", - "pg": "8.17.1", - "posthog-node": "5.23.0", + "pg": "8.18.0", + "posthog-node": "5.24.7", "qrcode.react": "4.2.0", - "react": "19.2.3", + "react": "19.2.4", "react-day-picker": "9.13.0", - "react-dom": "19.2.3", + "react-dom": "19.2.4", "react-easy-sort": "1.8.0", "react-hook-form": "7.71.1", "react-icons": "5.5.0", "rebuild": "0.1.2", "recharts": "2.15.4", "reodotdev": "1.0.0", - "resend": "6.8.0", + "resend": "6.9.1", "semver": "7.7.3", - "stripe": "20.2.0", + "stripe": "20.3.0", "swagger-ui-express": "5.0.1", "tailwind-merge": "3.4.0", "topojson-client": "3.1.0", @@ -136,14 +136,14 @@ "ws": "8.19.0", "yaml": "2.8.2", "yargs": "18.0.0", - "zod": "4.3.5", + "zod": "4.3.6", "zod-validation-error": "5.0.0" }, "devDependencies": { - "@dotenvx/dotenvx": "1.51.2", + "@dotenvx/dotenvx": "1.52.0", "@esbuild-plugins/tsconfig-paths": "0.1.2", "@tailwindcss/postcss": "4.1.18", - "@tanstack/react-query-devtools": "5.91.1", + "@tanstack/react-query-devtools": "5.91.3", "@types/better-sqlite3": "7.6.13", "@types/cookie-parser": "1.4.10", "@types/cors": "2.8.19", @@ -153,11 +153,11 @@ "@types/express-session": "1.18.2", "@types/jmespath": "0.15.2", "@types/jsonwebtoken": "9.0.10", - "@types/node": "24.10.2", - "@types/nodemailer": "7.0.4", + "@types/node": "25.2.0", + "@types/nodemailer": "7.0.9", "@types/nprogress": "0.2.3", "@types/pg": "8.16.0", - "@types/react": "19.2.7", + "@types/react": "19.2.10", "@types/react-dom": "19.2.3", "@types/semver": "7.7.1", "@types/swagger-ui-express": "4.1.8", @@ -170,12 +170,12 @@ "esbuild": "0.27.2", "esbuild-node-externals": "1.20.1", "postcss": "8.5.6", - "prettier": "3.8.0", + "prettier": "3.8.1", "react-email": "5.2.5", "tailwindcss": "4.1.18", "tsc-alias": "1.8.16", "tsx": "4.21.0", "typescript": "5.9.3", - "typescript-eslint": "8.53.1" + "typescript-eslint": "8.54.0" } } diff --git a/tsconfig.enterprise.json b/tsconfig.enterprise.json index 0b856fe0..60ed7c09 100644 --- a/tsconfig.enterprise.json +++ b/tsconfig.enterprise.json @@ -29,7 +29,7 @@ "name": "next" } ], - "target": "ES2022" + "target": "ES2024" }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], "exclude": ["node_modules"] diff --git a/tsconfig.oss.json b/tsconfig.oss.json index e32eabd3..f2157b29 100644 --- a/tsconfig.oss.json +++ b/tsconfig.oss.json @@ -29,7 +29,7 @@ "name": "next" } ], - "target": "ES2022" + "target": "ES2024" }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], "exclude": ["node_modules"] diff --git a/tsconfig.saas.json b/tsconfig.saas.json index 0b856fe0..60ed7c09 100644 --- a/tsconfig.saas.json +++ b/tsconfig.saas.json @@ -29,7 +29,7 @@ "name": "next" } ], - "target": "ES2022" + "target": "ES2024" }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], "exclude": ["node_modules"] From 13c011895d5bd2bad01be815cd7fe00889994835 Mon Sep 17 00:00:00 2001 From: Lokowitz Date: Mon, 2 Feb 2026 19:17:40 +0000 Subject: [PATCH 11/81] update packages and node --- Dockerfile.dev | 4 +++- package-lock.json | 16 ++++++++-------- package.json | 2 +- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/Dockerfile.dev b/Dockerfile.dev index c40775c2..3e5965fc 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -1,7 +1,9 @@ -FROM node:22-alpine +FROM node:24-alpine WORKDIR /app +RUN apk add --no-cache python3 make g++ + COPY package*.json ./ # Install dependencies diff --git a/package-lock.json b/package-lock.json index 1353ad7f..6e1f9c46 100644 --- a/package-lock.json +++ b/package-lock.json @@ -75,7 +75,7 @@ "lucide-react": "0.563.0", "maxmind": "5.0.5", "moment": "2.30.1", - "next": "15.5.9", + "next": "15.5.11", "next-intl": "4.8.2", "next-themes": "0.4.6", "nextjs-toploader": "3.9.17", @@ -3359,9 +3359,9 @@ } }, "node_modules/@next/env": { - "version": "15.5.9", - "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.9.tgz", - "integrity": "sha512-4GlTZ+EJM7WaW2HEZcyU317tIQDjkQIyENDLxYJfSWlfqguN+dHkZgyQTV/7ykvobU7yEH5gKvreNrH4B6QgIg==", + "version": "15.5.11", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.11.tgz", + "integrity": "sha512-g9s5SS9gC7GJCEOR3OV3zqs7C5VddqxP9X+/6BpMbdXRkqsWfFf2CJPBZNvNEtAkKTNuRgRXAgNxSAXzfLdaTg==", "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { @@ -16245,13 +16245,13 @@ } }, "node_modules/next": { - "version": "15.5.9", - "resolved": "https://registry.npmjs.org/next/-/next-15.5.9.tgz", - "integrity": "sha512-agNLK89seZEtC5zUHwtut0+tNrc0Xw4FT/Dg+B/VLEo9pAcS9rtTKpek3V6kVcVwsB2YlqMaHdfZL4eLEVYuCg==", + "version": "15.5.11", + "resolved": "https://registry.npmjs.org/next/-/next-15.5.11.tgz", + "integrity": "sha512-L2KPiKmqTDpRdeVDdPjhf43g2/VPe0NCNndq7OKDCgOLWtxe1kbr/zXGIZtYY7kZEAjRf7Bj/mwUFSr+tYC2Yg==", "license": "MIT", "peer": true, "dependencies": { - "@next/env": "15.5.9", + "@next/env": "15.5.11", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", diff --git a/package.json b/package.json index 38182620..38a98982 100644 --- a/package.json +++ b/package.json @@ -99,7 +99,7 @@ "lucide-react": "0.563.0", "maxmind": "5.0.5", "moment": "2.30.1", - "next": "15.5.9", + "next": "15.5.11", "next-intl": "4.8.2", "next-themes": "0.4.6", "nextjs-toploader": "3.9.17", From bb1a375484a1a4eccd117d97eb25eded67a2ab5a Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Wed, 4 Feb 2026 02:20:28 +0100 Subject: [PATCH 12/81] =?UTF-8?q?=E2=9C=A8=20paginate,=20search=20&=20filt?= =?UTF-8?q?er=20resources=20by=20enabled?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/routers/resource/listResources.ts | 80 +++++++++----- .../[orgId]/settings/resources/proxy/page.tsx | 21 +++- src/components/ProxyResourcesTable.tsx | 104 +++++++++++++++--- src/components/SitesTable.tsx | 14 ++- ...ta-table.tsx => controlled-data-table.tsx} | 42 ++++--- src/hooks/useNavigationContext.ts | 36 ++++++ 6 files changed, 227 insertions(+), 70 deletions(-) rename src/components/ui/{manual-data-table.tsx => controlled-data-table.tsx} (96%) create mode 100644 src/hooks/useNavigationContext.ts diff --git a/server/routers/resource/listResources.ts b/server/routers/resource/listResources.ts index c17e65a4..a60d27e6 100644 --- a/server/routers/resource/listResources.ts +++ b/server/routers/resource/listResources.ts @@ -17,7 +17,7 @@ import { import response from "@server/lib/response"; import HttpCode from "@server/types/HttpCode"; import createHttpError from "http-errors"; -import { sql, eq, or, inArray, and, count } from "drizzle-orm"; +import { sql, eq, or, inArray, and, count, ilike } from "drizzle-orm"; import logger from "@server/logger"; import { fromZodError } from "zod-validation-error"; import { OpenAPITags, registry } from "@server/openApi"; @@ -27,19 +27,30 @@ const listResourcesParamsSchema = z.strictObject({ }); const listResourcesSchema = z.object({ - limit: z - .string() + pageSize: z.coerce + .number() // for prettier formatting + .int() + .positive() .optional() - .default("1000") - .transform(Number) - .pipe(z.int().nonnegative()), - - offset: z - .string() + .catch(20) + .default(20), + page: z.coerce + .number() // for prettier formatting + .int() + .min(0) .optional() - .default("0") - .transform(Number) - .pipe(z.int().nonnegative()) + .catch(1) + .default(1), + query: z.string().optional(), + enabled: z + .enum(["true", "false"]) + .transform((v) => v === "true") + .optional() + .catch(undefined), + authState: z + .enum(["protected", "not_protected"]) + .optional() + .catch(undefined) }); // (resource fields + a single joined target) @@ -95,7 +106,7 @@ export type ResourceWithTargets = { }>; }; -function queryResources(accessibleResourceIds: number[], orgId: string) { +function queryResourcesBase() { return db .select({ resourceId: resources.resourceId, @@ -147,18 +158,12 @@ function queryResources(accessibleResourceIds: number[], orgId: string) { .leftJoin( targetHealthCheck, eq(targetHealthCheck.targetId, targets.targetId) - ) - .where( - and( - inArray(resources.resourceId, accessibleResourceIds), - eq(resources.orgId, orgId) - ) ); } export type ListResourcesResponse = { resources: ResourceWithTargets[]; - pagination: { total: number; limit: number; offset: number }; + pagination: { total: number; pageSize: number; page: number }; }; registry.registerPath({ @@ -190,7 +195,7 @@ export async function listResources( ) ); } - const { limit, offset } = parsedQuery.data; + const { page, pageSize, authState, enabled, query } = parsedQuery.data; const parsedParams = listResourcesParamsSchema.safeParse(req.params); if (!parsedParams.success) { @@ -252,14 +257,37 @@ export async function listResources( (resource) => resource.resourceId ); + let conditions = and( + and( + inArray(resources.resourceId, accessibleResourceIds), + eq(resources.orgId, orgId) + ) + ); + + if (query) { + conditions = and( + conditions, + or( + ilike(resources.name, "%" + query + "%"), + ilike(resources.fullDomain, "%" + query + "%") + ) + ); + } + if (typeof enabled !== "undefined") { + conditions = and(conditions, eq(resources.enabled, enabled)); + } + const countQuery: any = db .select({ count: count() }) .from(resources) - .where(inArray(resources.resourceId, accessibleResourceIds)); + .where(conditions); - const baseQuery = queryResources(accessibleResourceIds, orgId); + const baseQuery = queryResourcesBase(); - const rows: JoinedRow[] = await baseQuery.limit(limit).offset(offset); + const rows: JoinedRow[] = await baseQuery + .where(conditions) + .limit(pageSize) + .offset(pageSize * (page - 1)); // avoids TS issues with reduce/never[] const map = new Map(); @@ -324,8 +352,8 @@ export async function listResources( resources: resourcesList, pagination: { total: totalCount, - limit, - offset + pageSize, + page } }, success: true, diff --git a/src/app/[orgId]/settings/resources/proxy/page.tsx b/src/app/[orgId]/settings/resources/proxy/page.tsx index 408a9352..57505c53 100644 --- a/src/app/[orgId]/settings/resources/proxy/page.tsx +++ b/src/app/[orgId]/settings/resources/proxy/page.tsx @@ -16,7 +16,7 @@ import { cache } from "react"; export interface ProxyResourcesPageProps { params: Promise<{ orgId: string }>; - searchParams: Promise<{ view?: string }>; + searchParams: Promise>; } export default async function ProxyResourcesPage( @@ -24,14 +24,22 @@ export default async function ProxyResourcesPage( ) { const params = await props.params; const t = await getTranslations(); + const searchParams = new URLSearchParams(await props.searchParams); let resources: ListResourcesResponse["resources"] = []; + let pagination: ListResourcesResponse["pagination"] = { + total: 0, + page: 1, + pageSize: 20 + }; try { const res = await internal.get>( - `/org/${params.orgId}/resources`, + `/org/${params.orgId}/resources?${searchParams.toString()}`, await authCookieHeader() ); - resources = res.data.data.resources; + const responseData = res.data.data; + resources = responseData.resources; + pagination = responseData.pagination; } catch (e) {} let siteResources: ListAllSiteResourcesByOrgResponse["siteResources"] = []; @@ -104,9 +112,10 @@ export default async function ProxyResourcesPage( diff --git a/src/components/ProxyResourcesTable.tsx b/src/components/ProxyResourcesTable.tsx index 69b180c4..20eabc4d 100644 --- a/src/components/ProxyResourcesTable.tsx +++ b/src/components/ProxyResourcesTable.tsx @@ -31,8 +31,14 @@ import { } from "lucide-react"; import { useTranslations } from "next-intl"; import Link from "next/link"; -import { useRouter } from "next/navigation"; +import { usePathname, useRouter, useSearchParams } from "next/navigation"; import { useState, useTransition } from "react"; +import { ControlledDataTable } from "./ui/controlled-data-table"; +import type { PaginationState } from "@tanstack/react-table"; +import { useNavigationContext } from "@app/hooks/useNavigationContext"; +import { useDebouncedCallback } from "use-debounce"; +import z from "zod"; +import { ColumnFilterButton } from "./ColumnFilterButton"; export type TargetHealth = { targetId: number; @@ -117,18 +123,22 @@ function StatusIcon({ type ProxyResourcesTableProps = { resources: ResourceRow[]; orgId: string; - defaultSort?: { - id: string; - desc: boolean; - }; + pagination: PaginationState; + rowCount: number; }; export default function ProxyResourcesTable({ resources, orgId, - defaultSort + pagination, + rowCount }: ProxyResourcesTableProps) { const router = useRouter(); + const { + navigate: filter, + isNavigating: isFiltering, + searchParams + } = useNavigationContext(); const t = useTranslations(); const { env } = useEnvContext(); @@ -140,6 +150,7 @@ export default function ProxyResourcesTable({ useState(); const [isRefreshing, startTransition] = useTransition(); + const [isNavigatingToAddPage, startNavigation] = useTransition(); const refreshData = () => { startTransition(() => { @@ -236,7 +247,7 @@ export default function ProxyResourcesTable({ - + {monitoredTargets.length > 0 && ( <> {monitoredTargets.map((target) => ( @@ -456,7 +467,24 @@ export default function ProxyResourcesTable({ { accessorKey: "enabled", friendlyName: t("enabled"), - header: () => {t("enabled")}, + header: () => ( + + handleFilterChange("enabled", value) + } + searchPlaceholder={t("searchPlaceholder")} + emptyMessage={t("emptySearchOptions")} + label={t("enabled")} + className="p-3" + /> + ), cell: ({ row }) => ( { + searchParams.set("page", (newPage.pageIndex + 1).toString()); + searchParams.set("pageSize", newPage.pageSize.toString()); + filter({ + searchParams + }); + }; + + const handleSearchChange = useDebouncedCallback((query: string) => { + searchParams.set("query", query); + searchParams.delete("page"); + filter({ + searchParams + }); + }, 300); + return ( <> {selectedResource && ( @@ -547,21 +611,27 @@ export default function ProxyResourcesTable({ /> )} - - router.push(`/${orgId}/settings/resources/proxy/create`) + startNavigation(() => { + router.push( + `/${orgId}/settings/resources/proxy/create` + ); + }) } addButtonText={t("resourceAdd")} onRefresh={refreshData} - isRefreshing={isRefreshing} - defaultSort={defaultSort} - enableColumnVisibility={true} - persistColumnVisibility="proxy-resources" + isRefreshing={isRefreshing || isFiltering} + isNavigatingToAddPage={isNavigatingToAddPage} + enableColumnVisibility columnVisibility={{ niceId: false }} stickyLeftColumn="name" stickyRightColumn="actions" diff --git a/src/components/SitesTable.tsx b/src/components/SitesTable.tsx index 5076149f..76117776 100644 --- a/src/components/SitesTable.tsx +++ b/src/components/SitesTable.tsx @@ -33,9 +33,9 @@ import { usePathname, useRouter, useSearchParams } from "next/navigation"; import { useState, useTransition } from "react"; import { useDebouncedCallback } from "use-debounce"; import { - ManualDataTable, + ControlledDataTable, type ExtendedColumnDef -} from "./ui/manual-data-table"; +} from "./ui/controlled-data-table"; import { ColumnFilter } from "./ColumnFilter"; import { ColumnFilterButton } from "./ColumnFilterButton"; import z from "zod"; @@ -77,6 +77,7 @@ export default function SitesTable({ const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const [selectedSite, setSelectedSite] = useState(null); const [isRefreshing, startTransition] = useTransition(); + const [isNavigatingToAddPage, startNavigation] = useTransition(); const [getSortDirection, toggleSorting] = useSortColumn(); @@ -460,14 +461,19 @@ export default function SitesTable({ /> )} - router.push(`/${orgId}/settings/sites/create`)} + onAdd={() => + startNavigation(() => + router.push(`/${orgId}/settings/sites/create`) + ) + } + isNavigatingToAddPage={isNavigatingToAddPage} searchQuery={searchParams.get("query")?.toString()} onSearch={handleSearchChange} addButtonText={t("siteAdd")} diff --git a/src/components/ui/manual-data-table.tsx b/src/components/ui/controlled-data-table.tsx similarity index 96% rename from src/components/ui/manual-data-table.tsx rename to src/components/ui/controlled-data-table.tsx index 8653d465..c6fb505c 100644 --- a/src/components/ui/manual-data-table.tsx +++ b/src/components/ui/controlled-data-table.tsx @@ -64,7 +64,7 @@ type DataTableFilter = { export type DataTablePaginationUpdateFn = (newPage: PaginationState) => void; -type ManualDataTableProps = { +type ControlledDataTableProps = { columns: ExtendedColumnDef[]; rows: TData[]; tableId: string; @@ -72,12 +72,13 @@ type ManualDataTableProps = { onAdd?: () => void; onRefresh?: () => void; isRefreshing?: boolean; + isNavigatingToAddPage?: boolean; searchPlaceholder?: string; filters?: DataTableFilter[]; filterDisplayMode?: "label" | "calculated"; // Global filter display mode (can be overridden per filter) columnVisibility?: Record; enableColumnVisibility?: boolean; - onSearch: (input: string) => void; + onSearch?: (input: string) => void; searchQuery?: string; onPaginationChange: DataTablePaginationUpdateFn; stickyLeftColumn?: string; // Column ID or accessorKey for left sticky column @@ -86,7 +87,7 @@ type ManualDataTableProps = { pagination: PaginationState; }; -export function ManualDataTable({ +export function ControlledDataTable({ columns, rows, addButtonText, @@ -105,8 +106,9 @@ export function ManualDataTable({ searchQuery, onPaginationChange, stickyRightColumn, - rowCount -}: ManualDataTableProps) { + rowCount, + isNavigatingToAddPage +}: ControlledDataTableProps) { const t = useTranslations(); const [columnFilters, setColumnFilters] = useState([]); @@ -217,17 +219,20 @@ export function ManualDataTable({
-
- - onSearch(e.currentTarget.value) - } - className="w-full pl-8" - /> - -
+ {onSearch && ( +
+ + onSearch(e.currentTarget.value) + } + className="w-full pl-8" + /> + +
+ )} + {filters && filters.length > 0 && (
{filters.map((filter) => { @@ -326,7 +331,10 @@ export function ManualDataTable({ )} {onAdd && addButtonText && (
- diff --git a/src/hooks/useNavigationContext.ts b/src/hooks/useNavigationContext.ts new file mode 100644 index 00000000..71b7c552 --- /dev/null +++ b/src/hooks/useNavigationContext.ts @@ -0,0 +1,36 @@ +import { useSearchParams, usePathname, useRouter } from "next/navigation"; +import { useTransition } from "react"; + +export function useNavigationContext() { + const router = useRouter(); + const searchParams = useSearchParams(); + const path = usePathname(); + const [isNavigating, startTransition] = useTransition(); + + function navigate({ + searchParams: params, + pathname = path, + replace = false + }: { + pathname?: string; + searchParams?: URLSearchParams; + replace?: boolean; + }) { + startTransition(() => { + const fullPath = pathname + (params ? `?${params.toString()}` : ""); + + if (replace) { + router.replace(fullPath); + } else { + router.push(fullPath); + } + }); + } + + return { + pathname: path, + searchParams: new URLSearchParams(searchParams), // we want the search params to be writeable + navigate, + isNavigating + }; +} From 1fc40b301743d54967cdc8acdffdf9ae2dc0856a Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Wed, 4 Feb 2026 03:42:05 +0100 Subject: [PATCH 13/81] =?UTF-8?q?=E2=9C=A8=20filter=20by=20auth=20state?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/routers/resource/listResources.ts | 72 ++++++++++++++- src/components/ProxyResourcesTable.tsx | 107 ++++++++--------------- 2 files changed, 107 insertions(+), 72 deletions(-) diff --git a/server/routers/resource/listResources.ts b/server/routers/resource/listResources.ts index a60d27e6..a9c4d88c 100644 --- a/server/routers/resource/listResources.ts +++ b/server/routers/resource/listResources.ts @@ -17,7 +17,18 @@ import { import response from "@server/lib/response"; import HttpCode from "@server/types/HttpCode"; import createHttpError from "http-errors"; -import { sql, eq, or, inArray, and, count, ilike } from "drizzle-orm"; +import { + sql, + eq, + or, + inArray, + and, + count, + ilike, + asc, + not, + isNull +} from "drizzle-orm"; import logger from "@server/logger"; import { fromZodError } from "zod-validation-error"; import { OpenAPITags, registry } from "@server/openApi"; @@ -48,7 +59,7 @@ const listResourcesSchema = z.object({ .optional() .catch(undefined), authState: z - .enum(["protected", "not_protected"]) + .enum(["protected", "not_protected", "none"]) .optional() .catch(undefined) }); @@ -277,9 +288,63 @@ export async function listResources( conditions = and(conditions, eq(resources.enabled, enabled)); } + if (typeof authState !== "undefined") { + switch (authState) { + case "none": + conditions = and(conditions, eq(resources.http, false)); + break; + case "protected": + conditions = and( + conditions, + or( + eq(resources.sso, true), + eq(resources.emailWhitelistEnabled, true), + not(isNull(resourceHeaderAuth.headerAuthId)), + not(isNull(resourcePincode.pincodeId)), + not(isNull(resourcePassword.passwordId)) + ) + ); + break; + case "not_protected": + conditions = and( + conditions, + not(eq(resources.sso, true)), + not(eq(resources.emailWhitelistEnabled, true)), + isNull(resourceHeaderAuth.headerAuthId), + isNull(resourcePincode.pincodeId), + isNull(resourcePassword.passwordId) + ); + break; + } + } + const countQuery: any = db .select({ count: count() }) .from(resources) + .leftJoin( + resourcePassword, + eq(resourcePassword.resourceId, resources.resourceId) + ) + .leftJoin( + resourcePincode, + eq(resourcePincode.resourceId, resources.resourceId) + ) + .leftJoin( + resourceHeaderAuth, + eq(resourceHeaderAuth.resourceId, resources.resourceId) + ) + .leftJoin( + resourceHeaderAuthExtendedCompatibility, + eq( + resourceHeaderAuthExtendedCompatibility.resourceId, + resources.resourceId + ) + ) + .leftJoin(targets, eq(targets.resourceId, resources.resourceId)) + .leftJoin( + targetHealthCheck, + eq(targetHealthCheck.targetId, targets.targetId) + ) .where(conditions); const baseQuery = queryResourcesBase(); @@ -287,7 +352,8 @@ export async function listResources( const rows: JoinedRow[] = await baseQuery .where(conditions) .limit(pageSize) - .offset(pageSize * (page - 1)); + .offset(pageSize * (page - 1)) + .orderBy(asc(resources.resourceId)); // avoids TS issues with reduce/never[] const map = new Map(); diff --git a/src/components/ProxyResourcesTable.tsx b/src/components/ProxyResourcesTable.tsx index 20eabc4d..f57601b0 100644 --- a/src/components/ProxyResourcesTable.tsx +++ b/src/components/ProxyResourcesTable.tsx @@ -185,23 +185,24 @@ export default function ProxyResourcesTable({ }; async function toggleResourceEnabled(val: boolean, resourceId: number) { - await api - .post>( + try { + await api.post>( `resource/${resourceId}`, { enabled: val } - ) - .catch((e) => { - toast({ - variant: "destructive", - title: t("resourcesErrorUpdate"), - description: formatAxiosError( - e, - t("resourcesErrorUpdateDescription") - ) - }); + ); + router.refresh(); + } catch (e) { + toast({ + variant: "destructive", + title: t("resourcesErrorUpdate"), + description: formatAxiosError( + e, + t("resourcesErrorUpdateDescription") + ) }); + } } function TargetStatusCell({ targets }: { targets?: TargetHealth[] }) { @@ -313,38 +314,14 @@ export default function ProxyResourcesTable({ accessorKey: "name", enableHiding: false, friendlyName: t("name"), - header: ({ column }) => { - return ( - - ); - } + header: () => {t("name")} }, { id: "niceId", accessorKey: "nice", friendlyName: t("identifier"), enableHiding: true, - header: ({ column }) => { - return ( - - ); - }, + header: () => {t("identifier")}, cell: ({ row }) => { return {row.original.nice || "-"}; } @@ -370,19 +347,7 @@ export default function ProxyResourcesTable({ id: "status", accessorKey: "status", friendlyName: t("status"), - header: ({ column }) => { - return ( - - ); - }, + header: () => {t("status")}, cell: ({ row }) => { const resourceRow = row.original; return ; @@ -430,19 +395,23 @@ export default function ProxyResourcesTable({ { accessorKey: "authState", friendlyName: t("authentication"), - header: ({ column }) => { - return ( - - ); - }, + header: () => ( + + handleFilterChange("authState", value) + } + searchPlaceholder={t("searchPlaceholder")} + emptyMessage={t("emptySearchOptions")} + label={t("authentication")} + className="p-3" + /> + ), cell: ({ row }) => { const resourceRow = row.original; return ( @@ -487,16 +456,16 @@ export default function ProxyResourcesTable({ ), cell: ({ row }) => ( - toggleResourceEnabled(val, row.original.id) + startTransition(() => + toggleResourceEnabled(val, row.original.id) + ) } /> ) From 67949b4968c8fb3c05f9c38ae75ed159f6fdc052 Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Wed, 4 Feb 2026 04:10:08 +0100 Subject: [PATCH 14/81] =?UTF-8?q?=F0=9F=9A=A7=20wip:=20healthStatus?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/routers/resource/listResources.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/server/routers/resource/listResources.ts b/server/routers/resource/listResources.ts index a9c4d88c..dc19cf55 100644 --- a/server/routers/resource/listResources.ts +++ b/server/routers/resource/listResources.ts @@ -61,6 +61,10 @@ const listResourcesSchema = z.object({ authState: z .enum(["protected", "not_protected", "none"]) .optional() + .catch(undefined), + healthStatus: z + .enum(["online", "degraded", "offline", "unknown"]) + .optional() .catch(undefined) }); @@ -206,7 +210,8 @@ export async function listResources( ) ); } - const { page, pageSize, authState, enabled, query } = parsedQuery.data; + const { page, pageSize, authState, enabled, query, healthStatus } = + parsedQuery.data; const parsedParams = listResourcesParamsSchema.safeParse(req.params); if (!parsedParams.success) { @@ -318,6 +323,15 @@ export async function listResources( } } + if (typeof healthStatus !== "undefined") { + switch (healthStatus) { + case "online": + break; + default: + break; + } + } + const countQuery: any = db .select({ count: count() }) .from(resources) From d309ec249e42605684bc79f971daf0bf81bb9ae6 Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Thu, 5 Feb 2026 03:15:18 +0100 Subject: [PATCH 15/81] =?UTF-8?q?=E2=9C=A8=20filter=20resources=20by=20sta?= =?UTF-8?q?tus?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/db/pg/schema/schema.ts | 4 +- server/db/sqlite/schema/schema.ts | 4 +- server/routers/resource/listResources.ts | 215 +++++++++++++++-------- server/routers/site/listSites.ts | 8 +- src/components/ProxyResourcesTable.tsx | 34 +++- 5 files changed, 185 insertions(+), 80 deletions(-) diff --git a/server/db/pg/schema/schema.ts b/server/db/pg/schema/schema.ts index 3c957470..98c13479 100644 --- a/server/db/pg/schema/schema.ts +++ b/server/db/pg/schema/schema.ts @@ -187,7 +187,9 @@ export const targetHealthCheck = pgTable("targetHealthCheck", { hcFollowRedirects: boolean("hcFollowRedirects").default(true), hcMethod: varchar("hcMethod").default("GET"), hcStatus: integer("hcStatus"), // http code - hcHealth: text("hcHealth").default("unknown"), // "unknown", "healthy", "unhealthy" + hcHealth: text("hcHealth") + .$type<"unknown" | "healthy" | "unhealthy">() + .default("unknown"), // "unknown", "healthy", "unhealthy" hcTlsServerName: text("hcTlsServerName") }); diff --git a/server/db/sqlite/schema/schema.ts b/server/db/sqlite/schema/schema.ts index 4137db3c..f26ecc08 100644 --- a/server/db/sqlite/schema/schema.ts +++ b/server/db/sqlite/schema/schema.ts @@ -213,7 +213,9 @@ export const targetHealthCheck = sqliteTable("targetHealthCheck", { }).default(true), hcMethod: text("hcMethod").default("GET"), hcStatus: integer("hcStatus"), // http code - hcHealth: text("hcHealth").default("unknown"), // "unknown", "healthy", "unhealthy" + hcHealth: text("hcHealth") + .$type<"unknown" | "healthy" | "unhealthy">() + .default("unknown"), // "unknown", "healthy", "unhealthy" hcTlsServerName: text("hcTlsServerName") }); diff --git a/server/routers/resource/listResources.ts b/server/routers/resource/listResources.ts index dc19cf55..16b83e0e 100644 --- a/server/routers/resource/listResources.ts +++ b/server/routers/resource/listResources.ts @@ -27,7 +27,8 @@ import { ilike, asc, not, - isNull + isNull, + type SQL } from "drizzle-orm"; import logger from "@server/logger"; import { fromZodError } from "zod-validation-error"; @@ -63,7 +64,7 @@ const listResourcesSchema = z.object({ .optional() .catch(undefined), healthStatus: z - .enum(["online", "degraded", "offline", "unknown"]) + .enum(["no_targets", "healthy", "degraded", "offline", "unknown"]) .optional() .catch(undefined) }); @@ -86,13 +87,18 @@ type JoinedRow = { domainId: string | null; headerAuthId: number | null; - targetId: number | null; - targetIp: string | null; - targetPort: number | null; - targetEnabled: boolean | null; + // total_targets: number; + // healthy_targets: number; + // unhealthy_targets: number; + // unknown_targets: number; - hcHealth: string | null; - hcEnabled: boolean | null; + // targetId: number | null; + // targetIp: string | null; + // targetPort: number | null; + // targetEnabled: boolean | null; + + // hcHealth: string | null; + // hcEnabled: boolean | null; }; // grouped by resource with targets[]) @@ -117,10 +123,68 @@ export type ResourceWithTargets = { ip: string; port: number; enabled: boolean; - healthStatus?: "healthy" | "unhealthy" | "unknown"; + healthStatus: "healthy" | "unhealthy" | "unknown" | null; }>; }; +// Aggregate filters +const total_targets = count(targets.targetId); +const healthy_targets = sql`SUM( + CASE + WHEN ${targetHealthCheck.hcHealth} = 'healthy' THEN 1 + ELSE 0 + END + ) `; +const unknown_targets = sql`SUM( + CASE + WHEN ${targetHealthCheck.hcHealth} = 'unknown' THEN 1 + ELSE 0 + END + ) `; +const unhealthy_targets = sql`SUM( + CASE + WHEN ${targetHealthCheck.hcHealth} = 'unhealthy' THEN 1 + ELSE 0 + END + ) `; + +function countResourcesBase() { + return db + .select({ count: count() }) + .from(resources) + .leftJoin( + resourcePassword, + eq(resourcePassword.resourceId, resources.resourceId) + ) + .leftJoin( + resourcePincode, + eq(resourcePincode.resourceId, resources.resourceId) + ) + .leftJoin( + resourceHeaderAuth, + eq(resourceHeaderAuth.resourceId, resources.resourceId) + ) + .leftJoin( + resourceHeaderAuthExtendedCompatibility, + eq( + resourceHeaderAuthExtendedCompatibility.resourceId, + resources.resourceId + ) + ) + .leftJoin(targets, eq(targets.resourceId, resources.resourceId)) + .leftJoin( + targetHealthCheck, + eq(targetHealthCheck.targetId, targets.targetId) + ) + .groupBy( + resources.resourceId, + resourcePassword.passwordId, + resourcePincode.pincodeId, + resourceHeaderAuth.headerAuthId, + resourceHeaderAuthExtendedCompatibility.headerAuthExtendedCompatibilityId + ); +} + function queryResourcesBase() { return db .select({ @@ -140,14 +204,7 @@ function queryResourcesBase() { niceId: resources.niceId, headerAuthId: resourceHeaderAuth.headerAuthId, headerAuthExtendedCompatibilityId: - resourceHeaderAuthExtendedCompatibility.headerAuthExtendedCompatibilityId, - targetId: targets.targetId, - targetIp: targets.ip, - targetPort: targets.port, - targetEnabled: targets.enabled, - - hcHealth: targetHealthCheck.hcHealth, - hcEnabled: targetHealthCheck.hcEnabled + resourceHeaderAuthExtendedCompatibility.headerAuthExtendedCompatibilityId }) .from(resources) .leftJoin( @@ -173,6 +230,13 @@ function queryResourcesBase() { .leftJoin( targetHealthCheck, eq(targetHealthCheck.targetId, targets.targetId) + ) + .groupBy( + resources.resourceId, + resourcePassword.passwordId, + resourcePincode.pincodeId, + resourceHeaderAuth.headerAuthId, + resourceHeaderAuthExtendedCompatibility.headerAuthExtendedCompatibilityId ); } @@ -323,45 +387,52 @@ export async function listResources( } } + let aggregateFilters: SQL | null | undefined = null; + if (typeof healthStatus !== "undefined") { switch (healthStatus) { - case "online": + case "healthy": + aggregateFilters = and( + sql`${total_targets} > 0`, + sql`${healthy_targets} = ${total_targets}` + ); break; - default: + case "degraded": + aggregateFilters = and( + sql`${total_targets} > 0`, + sql`${unhealthy_targets} > 0` + ); + break; + case "no_targets": + aggregateFilters = sql`${total_targets} = 0`; + break; + case "offline": + aggregateFilters = and( + sql`${total_targets} > 0`, + sql`${healthy_targets} = 0`, + sql`${unhealthy_targets} = ${total_targets}` + ); + break; + case "unknown": + aggregateFilters = and( + sql`${total_targets} > 0`, + sql`${unknown_targets} = ${total_targets}` + ); break; } } - const countQuery: any = db - .select({ count: count() }) - .from(resources) - .leftJoin( - resourcePassword, - eq(resourcePassword.resourceId, resources.resourceId) - ) - .leftJoin( - resourcePincode, - eq(resourcePincode.resourceId, resources.resourceId) - ) - .leftJoin( - resourceHeaderAuth, - eq(resourceHeaderAuth.resourceId, resources.resourceId) - ) - .leftJoin( - resourceHeaderAuthExtendedCompatibility, - eq( - resourceHeaderAuthExtendedCompatibility.resourceId, - resources.resourceId - ) - ) - .leftJoin(targets, eq(targets.resourceId, resources.resourceId)) - .leftJoin( - targetHealthCheck, - eq(targetHealthCheck.targetId, targets.targetId) - ) - .where(conditions); + let baseQuery = queryResourcesBase(); + let countQuery = countResourcesBase().where(conditions); - const baseQuery = queryResourcesBase(); + if (aggregateFilters) { + // @ts-expect-error idk why this is causing a type error + baseQuery = baseQuery.having(aggregateFilters); + } + if (aggregateFilters) { + // @ts-expect-error idk why this is causing a type error + countQuery = countQuery.having(aggregateFilters); + } const rows: JoinedRow[] = await baseQuery .where(conditions) @@ -369,6 +440,27 @@ export async function listResources( .offset(pageSize * (page - 1)) .orderBy(asc(resources.resourceId)); + const resourceIdList = rows.map((row) => row.resourceId); + const allResourceTargets = + resourceIdList.length === 0 + ? [] + : await db + .select({ + targetId: targets.targetId, + resourceId: targets.resourceId, + ip: targets.ip, + port: targets.port, + enabled: targets.enabled, + healthStatus: targetHealthCheck.hcHealth, + hcEnabled: targetHealthCheck.hcEnabled + }) + .from(targets) + .where(sql`${targets.resourceId} in ${resourceIdList}`) + .leftJoin( + targetHealthCheck, + eq(targetHealthCheck.targetId, targets.targetId) + ); + // avoids TS issues with reduce/never[] const map = new Map(); @@ -396,30 +488,9 @@ export async function listResources( map.set(row.resourceId, entry); } - if ( - row.targetId != null && - row.targetIp && - row.targetPort != null && - row.targetEnabled != null - ) { - let healthStatus: "healthy" | "unhealthy" | "unknown" = - "unknown"; - - if (row.hcEnabled && row.hcHealth) { - healthStatus = row.hcHealth as - | "healthy" - | "unhealthy" - | "unknown"; - } - - entry.targets.push({ - targetId: row.targetId, - ip: row.targetIp, - port: row.targetPort, - enabled: row.targetEnabled, - healthStatus: healthStatus - }); - } + entry.targets = allResourceTargets.filter( + (t) => t.resourceId === entry.resourceId + ); } const resourcesList: ResourceWithTargets[] = Array.from(map.values()); diff --git a/server/routers/site/listSites.ts b/server/routers/site/listSites.ts index 1cc54fab..8a0a85ab 100644 --- a/server/routers/site/listSites.ts +++ b/server/routers/site/listSites.ts @@ -111,6 +111,9 @@ const listSitesSchema = z.object({ .catch(undefined) }); +function countSitesBase() { + return db.select({ count: count() }).from(sites); +} function querySitesBase() { return db .select({ @@ -242,10 +245,7 @@ export async function listSites( conditions = and(conditions, eq(sites.online, online)); } - const countQuery = db - .select({ count: count() }) - .from(sites) - .where(conditions); + const countQuery = countSitesBase().where(conditions); const siteListQuery = baseQuery .where(conditions) diff --git a/src/components/ProxyResourcesTable.tsx b/src/components/ProxyResourcesTable.tsx index f57601b0..ca8f0443 100644 --- a/src/components/ProxyResourcesTable.tsx +++ b/src/components/ProxyResourcesTable.tsx @@ -45,7 +45,7 @@ export type TargetHealth = { ip: string; port: number; enabled: boolean; - healthStatus?: "healthy" | "unhealthy" | "unknown"; + healthStatus: "healthy" | "unhealthy" | "unknown" | null; }; export type ResourceRow = { @@ -347,7 +347,33 @@ export default function ProxyResourcesTable({ id: "status", accessorKey: "status", friendlyName: t("status"), - header: () => {t("status")}, + header: () => ( + + handleFilterChange("healthStatus", value) + } + searchPlaceholder={t("searchPlaceholder")} + emptyMessage={t("emptySearchOptions")} + label={t("status")} + className="p-3" + /> + ), cell: ({ row }) => { const resourceRow = row.original; return ; @@ -558,6 +584,10 @@ export default function ProxyResourcesTable({ }); }, 300); + console.log({ + rowCount + }); + return ( <> {selectedResource && ( From 748af1d8cb57789726a5e5519244640b448c5904 Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Thu, 5 Feb 2026 05:21:25 +0100 Subject: [PATCH 16/81] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20cleanup=20code=20for?= =?UTF-8?q?=20searching=20&=20filtering?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/routers/resource/listResources.ts | 69 +++++------------------- server/routers/site/listSites.ts | 31 ++++++----- 2 files changed, 30 insertions(+), 70 deletions(-) diff --git a/server/routers/resource/listResources.ts b/server/routers/resource/listResources.ts index 16b83e0e..add3b2b5 100644 --- a/server/routers/resource/listResources.ts +++ b/server/routers/resource/listResources.ts @@ -148,43 +148,6 @@ const unhealthy_targets = sql`SUM( END ) `; -function countResourcesBase() { - return db - .select({ count: count() }) - .from(resources) - .leftJoin( - resourcePassword, - eq(resourcePassword.resourceId, resources.resourceId) - ) - .leftJoin( - resourcePincode, - eq(resourcePincode.resourceId, resources.resourceId) - ) - .leftJoin( - resourceHeaderAuth, - eq(resourceHeaderAuth.resourceId, resources.resourceId) - ) - .leftJoin( - resourceHeaderAuthExtendedCompatibility, - eq( - resourceHeaderAuthExtendedCompatibility.resourceId, - resources.resourceId - ) - ) - .leftJoin(targets, eq(targets.resourceId, resources.resourceId)) - .leftJoin( - targetHealthCheck, - eq(targetHealthCheck.targetId, targets.targetId) - ) - .groupBy( - resources.resourceId, - resourcePassword.passwordId, - resourcePincode.pincodeId, - resourceHeaderAuth.headerAuthId, - resourceHeaderAuthExtendedCompatibility.headerAuthExtendedCompatibilityId - ); -} - function queryResourcesBase() { return db .select({ @@ -422,23 +385,20 @@ export async function listResources( } } - let baseQuery = queryResourcesBase(); - let countQuery = countResourcesBase().where(conditions); - - if (aggregateFilters) { - // @ts-expect-error idk why this is causing a type error - baseQuery = baseQuery.having(aggregateFilters); - } - if (aggregateFilters) { - // @ts-expect-error idk why this is causing a type error - countQuery = countQuery.having(aggregateFilters); - } - - const rows: JoinedRow[] = await baseQuery + const baseQuery = queryResourcesBase() .where(conditions) - .limit(pageSize) - .offset(pageSize * (page - 1)) - .orderBy(asc(resources.resourceId)); + .having(aggregateFilters ?? sql`1 = 1`); + + // we need to add `as` so that drizzle filters the result as a subquery + const countQuery = db.$count(baseQuery.as("filtered_resources")); + + const [rows, totalCount] = await Promise.all([ + baseQuery + .limit(pageSize) + .offset(pageSize * (page - 1)) + .orderBy(asc(resources.resourceId)), + countQuery + ]); const resourceIdList = rows.map((row) => row.resourceId); const allResourceTargets = @@ -495,9 +455,6 @@ export async function listResources( const resourcesList: ResourceWithTargets[] = Array.from(map.values()); - const totalCountResult = await countQuery; - const totalCount = totalCountResult[0]?.count ?? 0; - return response(res, { data: { resources: resourcesList, diff --git a/server/routers/site/listSites.ts b/server/routers/site/listSites.ts index 8a0a85ab..e27a328a 100644 --- a/server/routers/site/listSites.ts +++ b/server/routers/site/listSites.ts @@ -226,7 +226,6 @@ export async function listSites( parsedQuery.data; const accessibleSiteIds = accessibleSites.map((site) => site.siteId); - const baseQuery = querySitesBase(); let conditions = and( inArray(sites.siteId, accessibleSiteIds), @@ -245,27 +244,31 @@ export async function listSites( conditions = and(conditions, eq(sites.online, online)); } - const countQuery = countSitesBase().where(conditions); + const baseQuery = querySitesBase().where(conditions); + + // we need to add `as` so that drizzle filters the result as a subquery + const countQuery = db.$count(baseQuery.as("filtered_sites")); const siteListQuery = baseQuery - .where(conditions) .limit(pageSize) - .offset(pageSize * (page - 1)); - - if (sort_by) { - siteListQuery.orderBy( - order === "asc" ? asc(sites[sort_by]) : desc(sites[sort_by]) + .offset(pageSize * (page - 1)) + .orderBy( + sort_by + ? order === "asc" + ? asc(sites[sort_by]) + : desc(sites[sort_by]) + : asc(sites.siteId) ); - } - const totalCountResult = await countQuery; - const totalCount = totalCountResult[0].count; + + const [totalCount, rows] = await Promise.all([ + countQuery, + siteListQuery + ]); // Get latest version asynchronously without blocking the response const latestNewtVersionPromise = getLatestNewtVersion(); - const sitesWithUpdates: SiteWithUpdateAvailable[] = ( - await siteListQuery - ).map((site) => { + const sitesWithUpdates: SiteWithUpdateAvailable[] = rows.map((site) => { const siteWithUpdate: SiteWithUpdateAvailable = { ...site }; // Initially set to false, will be updated if version check succeeds siteWithUpdate.newtUpdateAvailable = false; From 609ffccd67641f6b642edc1fd92d781517923fc4 Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Thu, 5 Feb 2026 05:35:59 +0100 Subject: [PATCH 17/81] =?UTF-8?q?=F0=9F=8F=B7=EF=B8=8F=20fix=20typescript?= =?UTF-8?q?=20error?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/routers/target/handleHealthcheckStatusMessage.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/server/routers/target/handleHealthcheckStatusMessage.ts b/server/routers/target/handleHealthcheckStatusMessage.ts index 2bfcff19..01cbdea8 100644 --- a/server/routers/target/handleHealthcheckStatusMessage.ts +++ b/server/routers/target/handleHealthcheckStatusMessage.ts @@ -105,7 +105,10 @@ export const handleHealthcheckStatusMessage: MessageHandler = async ( await db .update(targetHealthCheck) .set({ - hcHealth: healthStatus.status + hcHealth: healthStatus.status as + | "unknown" + | "healthy" + | "unhealthy" }) .where(eq(targetHealthCheck.targetId, targetIdNum)) .execute(); From 941d5c08e34c1042b79d31e5e0ec1940d31d7095 Mon Sep 17 00:00:00 2001 From: Lokowitz Date: Thu, 5 Feb 2026 19:26:36 +0000 Subject: [PATCH 18/81] upgrade packages --- package-lock.json | 2327 +++++---------------------------------------- package.json | 24 +- 2 files changed, 229 insertions(+), 2122 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5c8eac1a..5aee9af2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "SEE LICENSE IN LICENSE AND README.md", "dependencies": { "@asteasolutions/zod-to-openapi": "8.4.0", - "@aws-sdk/client-s3": "3.980.0", + "@aws-sdk/client-s3": "3.983.0", "@faker-js/faker": "10.2.0", "@headlessui/react": "2.2.9", "@hookform/resolvers": "5.2.2", @@ -36,9 +36,9 @@ "@radix-ui/react-tabs": "1.1.13", "@radix-ui/react-toast": "1.2.15", "@radix-ui/react-tooltip": "1.2.8", - "@react-email/components": "1.0.6", + "@react-email/components": "1.0.7", "@react-email/render": "2.0.4", - "@react-email/tailwind": "2.0.3", + "@react-email/tailwind": "2.0.4", "@simplewebauthn/browser": "13.2.2", "@simplewebauthn/server": "13.2.2", "@tailwindcss/forms": "0.5.11", @@ -52,7 +52,7 @@ "clsx": "2.1.1", "cmdk": "1.1.1", "cookie-parser": "1.4.7", - "cors": "2.8.5", + "cors": "2.8.6", "crypto-js": "4.2.0", "d3": "7.9.0", "date-fns": "4.1.0", @@ -61,7 +61,7 @@ "eslint-config-next": "16.1.6", "express": "5.2.1", "express-rate-limit": "8.2.1", - "glob": "13.0.0", + "glob": "13.0.1", "helmet": "8.1.0", "http-errors": "2.0.1", "input-otp": "1.4.2", @@ -77,10 +77,10 @@ "next-themes": "0.4.6", "nextjs-toploader": "3.9.17", "node-cache": "5.1.2", - "nodemailer": "7.0.11", + "nodemailer": "8.0.0", "oslo": "1.2.1", "pg": "8.18.0", - "posthog-node": "5.24.7", + "posthog-node": "5.24.10", "qrcode.react": "4.2.0", "react": "19.2.4", "react-day-picker": "9.13.0", @@ -91,8 +91,8 @@ "recharts": "2.15.4", "reodotdev": "1.0.0", "resend": "6.9.1", - "semver": "7.7.3", - "stripe": "20.3.0", + "semver": "7.7.4", + "stripe": "20.3.1", "swagger-ui-express": "5.0.1", "tailwind-merge": "3.4.0", "topojson-client": "3.1.0", @@ -123,11 +123,11 @@ "@types/jmespath": "0.15.2", "@types/js-yaml": "4.0.9", "@types/jsonwebtoken": "9.0.10", - "@types/node": "25.2.0", + "@types/node": "25.2.1", "@types/nodemailer": "7.0.9", "@types/nprogress": "0.2.3", "@types/pg": "8.16.0", - "@types/react": "19.2.10", + "@types/react": "19.2.13", "@types/react-dom": "19.2.3", "@types/semver": "7.7.1", "@types/swagger-ui-express": "4.1.8", @@ -140,7 +140,7 @@ "esbuild-node-externals": "1.20.1", "postcss": "8.5.6", "prettier": "3.8.1", - "react-email": "5.2.5", + "react-email": "5.2.7", "tailwindcss": "4.1.18", "tsc-alias": "1.8.16", "tsx": "4.21.0", @@ -389,32 +389,32 @@ } }, "node_modules/@aws-sdk/client-s3": { - "version": "3.980.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.980.0.tgz", - "integrity": "sha512-ch8QqKehyn1WOYbd8LyDbWjv84Z9OEj9qUxz8q3IOCU3ftAVkVR0wAuN96a1xCHnpOJcQZo3rOB08RlyKdkGxQ==", + "version": "3.983.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.983.0.tgz", + "integrity": "sha512-V40PT2irPh3lj+Z95tZI6batVrjaTrWEOXRNVBuoZSgpM3Ak1jiE9ZXwVLkMcbb9/GH4xVpB3EsGM7gbxmgFLQ==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.5", - "@aws-sdk/credential-provider-node": "^3.972.4", + "@aws-sdk/core": "^3.973.6", + "@aws-sdk/credential-provider-node": "^3.972.5", "@aws-sdk/middleware-bucket-endpoint": "^3.972.3", "@aws-sdk/middleware-expect-continue": "^3.972.3", - "@aws-sdk/middleware-flexible-checksums": "^3.972.3", + "@aws-sdk/middleware-flexible-checksums": "^3.972.4", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-location-constraint": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", - "@aws-sdk/middleware-sdk-s3": "^3.972.5", + "@aws-sdk/middleware-sdk-s3": "^3.972.6", "@aws-sdk/middleware-ssec": "^3.972.3", - "@aws-sdk/middleware-user-agent": "^3.972.5", + "@aws-sdk/middleware-user-agent": "^3.972.6", "@aws-sdk/region-config-resolver": "^3.972.3", - "@aws-sdk/signature-v4-multi-region": "3.980.0", + "@aws-sdk/signature-v4-multi-region": "3.983.0", "@aws-sdk/types": "^3.973.1", - "@aws-sdk/util-endpoints": "3.980.0", + "@aws-sdk/util-endpoints": "3.983.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", - "@aws-sdk/util-user-agent-node": "^3.972.3", + "@aws-sdk/util-user-agent-node": "^3.972.4", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.22.0", "@smithy/eventstream-serde-browser": "^4.2.8", @@ -455,23 +455,23 @@ } }, "node_modules/@aws-sdk/client-sso": { - "version": "3.980.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.980.0.tgz", - "integrity": "sha512-AhNXQaJ46C1I+lQ+6Kj+L24il5K9lqqIanJd8lMszPmP7bLnmX0wTKK0dxywcvrLdij3zhWttjAKEBNgLtS8/A==", + "version": "3.982.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.982.0.tgz", + "integrity": "sha512-qJrIiivmvujdGqJ0ldSUvhN3k3N7GtPesoOI1BSt0fNXovVnMz4C/JmnkhZihU7hJhDvxJaBROLYTU+lpild4w==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.5", + "@aws-sdk/core": "^3.973.6", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", - "@aws-sdk/middleware-user-agent": "^3.972.5", + "@aws-sdk/middleware-user-agent": "^3.972.6", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/types": "^3.973.1", - "@aws-sdk/util-endpoints": "3.980.0", + "@aws-sdk/util-endpoints": "3.982.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", - "@aws-sdk/util-user-agent-node": "^3.972.3", + "@aws-sdk/util-user-agent-node": "^3.972.4", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.22.0", "@smithy/fetch-http-handler": "^5.3.9", @@ -503,14 +503,30 @@ "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/core": { - "version": "3.973.5", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.5.tgz", - "integrity": "sha512-IMM7xGfLGW6lMvubsA4j6BHU5FPgGAxoQ/NA63KqNLMwTS+PeMBcx8DPHL12Vg6yqOZnqok9Mu4H2BdQyq7gSA==", + "node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/util-endpoints": { + "version": "3.982.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.982.0.tgz", + "integrity": "sha512-M27u8FJP7O0Of9hMWX5dipp//8iglmV9jr7R8SR8RveU+Z50/8TqH68Tu6wUWBGMfXjzbVwn1INIAO5lZrlxXQ==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "^3.973.1", - "@aws-sdk/xml-builder": "^3.972.2", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-endpoints": "^3.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.973.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.6.tgz", + "integrity": "sha512-pz4ZOw3BLG0NdF25HoB9ymSYyPbMiIjwQJ2aROXRhAzt+b+EOxStfFv8s5iZyP6Kiw7aYhyWxj5G3NhmkoOTKw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/xml-builder": "^3.972.4", "@smithy/core": "^3.22.0", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", @@ -541,12 +557,12 @@ } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.972.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.3.tgz", - "integrity": "sha512-OBYNY4xQPq7Rx+oOhtyuyO0AQvdJSpXRg7JuPNBJH4a1XXIzJQl4UHQTPKZKwfJXmYLpv4+OkcFen4LYmDPd3g==", + "version": "3.972.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.4.tgz", + "integrity": "sha512-/8dnc7+XNMmViEom2xsNdArQxQPSgy4Z/lm6qaFPTrMFesT1bV3PsBhb19n09nmxHdrtQskYmViddUIjUQElXg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.5", + "@aws-sdk/core": "^3.973.6", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/types": "^4.12.0", @@ -557,12 +573,12 @@ } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.5", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.5.tgz", - "integrity": "sha512-GpvBgEmSZPvlDekd26Zi+XsI27Qz7y0utUx0g2fSTSiDzhnd1FSa1owuodxR0BcUKNL7U2cOVhhDxgZ4iSoPVg==", + "version": "3.972.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.6.tgz", + "integrity": "sha512-5ERWqRljiZv44AIdvIRQ3k+EAV0Sq2WeJHvXuK7gL7bovSxOf8Al7MLH7Eh3rdovH4KHFnlIty7J71mzvQBl5Q==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.5", + "@aws-sdk/core": "^3.973.6", "@aws-sdk/types": "^3.973.1", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/node-http-handler": "^4.4.8", @@ -578,19 +594,19 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.972.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.3.tgz", - "integrity": "sha512-rMQAIxstP7cLgYfsRGrGOlpyMl0l8JL2mcke3dsIPLWke05zKOFyR7yoJzWCsI/QiIxjRbxpvPiAeKEA6CoYkg==", + "version": "3.972.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.4.tgz", + "integrity": "sha512-eRUg+3HaUKuXWn/lEMirdiA5HOKmEl8hEHVuszIDt2MMBUKgVX5XNGmb3XmbgU17h6DZ+RtjbxQpjhz3SbTjZg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.5", - "@aws-sdk/credential-provider-env": "^3.972.3", - "@aws-sdk/credential-provider-http": "^3.972.5", - "@aws-sdk/credential-provider-login": "^3.972.3", - "@aws-sdk/credential-provider-process": "^3.972.3", - "@aws-sdk/credential-provider-sso": "^3.972.3", - "@aws-sdk/credential-provider-web-identity": "^3.972.3", - "@aws-sdk/nested-clients": "3.980.0", + "@aws-sdk/core": "^3.973.6", + "@aws-sdk/credential-provider-env": "^3.972.4", + "@aws-sdk/credential-provider-http": "^3.972.6", + "@aws-sdk/credential-provider-login": "^3.972.4", + "@aws-sdk/credential-provider-process": "^3.972.4", + "@aws-sdk/credential-provider-sso": "^3.972.4", + "@aws-sdk/credential-provider-web-identity": "^3.972.4", + "@aws-sdk/nested-clients": "3.982.0", "@aws-sdk/types": "^3.973.1", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/property-provider": "^4.2.8", @@ -603,13 +619,13 @@ } }, "node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.3.tgz", - "integrity": "sha512-Gc3O91iVvA47kp2CLIXOwuo5ffo1cIpmmyIewcYjAcvurdFHQ8YdcBe1KHidnbbBO4/ZtywGBACsAX5vr3UdoA==", + "version": "3.972.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.4.tgz", + "integrity": "sha512-nLGjXuvWWDlQAp505xIONI7Gam0vw2p7Qu3P6on/W2q7rjJXtYjtpHbcsaOjJ/pAju3eTvEQuSuRedcRHVQIAQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.5", - "@aws-sdk/nested-clients": "3.980.0", + "@aws-sdk/core": "^3.973.6", + "@aws-sdk/nested-clients": "3.982.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", @@ -622,17 +638,17 @@ } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.4", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.4.tgz", - "integrity": "sha512-UwerdzosMSY7V5oIZm3NsMDZPv2aSVzSkZxYxIOWHBeKTZlUqW7XpHtJMZ4PZpJ+HMRhgP+MDGQx4THndgqJfQ==", + "version": "3.972.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.5.tgz", + "integrity": "sha512-VWXKgSISQCI2GKN3zakTNHSiZ0+mux7v6YHmmbLQp/o3fvYUQJmKGcLZZzg2GFA+tGGBStplra9VFNf/WwxpYg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "^3.972.3", - "@aws-sdk/credential-provider-http": "^3.972.5", - "@aws-sdk/credential-provider-ini": "^3.972.3", - "@aws-sdk/credential-provider-process": "^3.972.3", - "@aws-sdk/credential-provider-sso": "^3.972.3", - "@aws-sdk/credential-provider-web-identity": "^3.972.3", + "@aws-sdk/credential-provider-env": "^3.972.4", + "@aws-sdk/credential-provider-http": "^3.972.6", + "@aws-sdk/credential-provider-ini": "^3.972.4", + "@aws-sdk/credential-provider-process": "^3.972.4", + "@aws-sdk/credential-provider-sso": "^3.972.4", + "@aws-sdk/credential-provider-web-identity": "^3.972.4", "@aws-sdk/types": "^3.973.1", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/property-provider": "^4.2.8", @@ -645,12 +661,12 @@ } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.972.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.3.tgz", - "integrity": "sha512-xkSY7zjRqeVc6TXK2xr3z1bTLm0wD8cj3lAkproRGaO4Ku7dPlKy843YKnHrUOUzOnMezdZ4xtmFc0eKIDTo2w==", + "version": "3.972.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.4.tgz", + "integrity": "sha512-TCZpWUnBQN1YPk6grvd5x419OfXjHvhj5Oj44GYb84dOVChpg/+2VoEj+YVA4F4E/6huQPNnX7UYbTtxJqgihw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.5", + "@aws-sdk/core": "^3.973.6", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", @@ -662,14 +678,14 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.972.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.3.tgz", - "integrity": "sha512-8Ww3F5Ngk8dZ6JPL/V5LhCU1BwMfQd3tLdoEuzaewX8FdnT633tPr+KTHySz9FK7fFPcz5qG3R5edVEhWQD4AA==", + "version": "3.972.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.4.tgz", + "integrity": "sha512-wzsGwv9mKlwJ3vHLyembBvGE/5nPUIwRR2I51B1cBV4Cb4ql9nIIfpmHzm050XYTY5fqTOKJQnhLj7zj89VG8g==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-sso": "3.980.0", - "@aws-sdk/core": "^3.973.5", - "@aws-sdk/token-providers": "3.980.0", + "@aws-sdk/client-sso": "3.982.0", + "@aws-sdk/core": "^3.973.6", + "@aws-sdk/token-providers": "3.982.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", @@ -681,13 +697,13 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.972.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.3.tgz", - "integrity": "sha512-62VufdcH5rRfiRKZRcf1wVbbt/1jAntMj1+J0qAd+r5pQRg2t0/P9/Rz16B1o5/0Se9lVL506LRjrhIJAhYBfA==", + "version": "3.972.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.4.tgz", + "integrity": "sha512-hIzw2XzrG8jzsUSEatehmpkd5rWzASg5IHUfA+m01k/RtvfAML7ZJVVohuKdhAYx+wV2AThLiQJVzqn7F0khrw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.5", - "@aws-sdk/nested-clients": "3.980.0", + "@aws-sdk/core": "^3.973.6", + "@aws-sdk/nested-clients": "3.982.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", @@ -732,15 +748,15 @@ } }, "node_modules/@aws-sdk/middleware-flexible-checksums": { - "version": "3.972.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.972.3.tgz", - "integrity": "sha512-MkNGJ6qB9kpsLwL18kC/ZXppsJbftHVGCisqpEVbTQsum8CLYDX1Bmp/IvhRGNxsqCO2w9/4PwhDKBjG3Uvr4Q==", + "version": "3.972.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.972.4.tgz", + "integrity": "sha512-xOxsUkF3O3BtIe3tf54OpPo94eZepjFm3z0Dd2TZKbsPxMiRTFXurC04wJ58o/wPW9YHVO9VqZik3MfoPfrKlw==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", "@aws-crypto/util": "5.2.0", - "@aws-sdk/core": "^3.973.5", + "@aws-sdk/core": "^3.973.6", "@aws-sdk/crc64-nvme": "3.972.0", "@aws-sdk/types": "^3.973.1", "@smithy/is-array-buffer": "^4.2.0", @@ -816,12 +832,12 @@ } }, "node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.972.5", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.5.tgz", - "integrity": "sha512-3IgeIDiQ15tmMBFIdJ1cTy3A9rXHGo+b9p22V38vA3MozeMyVC8VmCYdDLA0iMWo4VHA9LDJTgCM0+xU3wjBOg==", + "version": "3.972.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.6.tgz", + "integrity": "sha512-Xq7wM6kbgJN1UO++8dvH/efPb1nTwWqFCpZCR7RCLOETP7xAUAhVo7JmsCnML5Di/iC4Oo5VrJ4QmkYcMZniLw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.5", + "@aws-sdk/core": "^3.973.6", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-arn-parser": "^3.972.2", "@smithy/core": "^3.22.0", @@ -855,14 +871,14 @@ } }, "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.972.5", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.5.tgz", - "integrity": "sha512-TVZQ6PWPwQbahUI8V+Er+gS41ctIawcI/uMNmQtQ7RMcg3JYn6gyKAFKUb3HFYx2OjYlx1u11sETSwwEUxVHTg==", + "version": "3.972.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.6.tgz", + "integrity": "sha512-TehLN8W/kivl0U9HcS+keryElEWORROpghDXZBLfnb40DXM7hx/i+7OOjkogXQOF3QtUraJVRkHQ07bPhrWKlw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.5", + "@aws-sdk/core": "^3.973.6", "@aws-sdk/types": "^3.973.1", - "@aws-sdk/util-endpoints": "3.980.0", + "@aws-sdk/util-endpoints": "3.982.0", "@smithy/core": "^3.22.0", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", @@ -872,24 +888,40 @@ "node": ">=20.0.0" } }, + "node_modules/@aws-sdk/middleware-user-agent/node_modules/@aws-sdk/util-endpoints": { + "version": "3.982.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.982.0.tgz", + "integrity": "sha512-M27u8FJP7O0Of9hMWX5dipp//8iglmV9jr7R8SR8RveU+Z50/8TqH68Tu6wUWBGMfXjzbVwn1INIAO5lZrlxXQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-endpoints": "^3.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/nested-clients": { - "version": "3.980.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.980.0.tgz", - "integrity": "sha512-/dONY5xc5/CCKzOqHZCTidtAR4lJXWkGefXvTRKdSKMGaYbbKsxDckisd6GfnvPSLxWtvQzwgRGRutMRoYUApQ==", + "version": "3.982.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.982.0.tgz", + "integrity": "sha512-VVkaH27digrJfdVrT64rjkllvOp4oRiZuuJvrylLXAKl18ujToJR7AqpDldL/LS63RVne3QWIpkygIymxFtliQ==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.5", + "@aws-sdk/core": "^3.973.6", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", - "@aws-sdk/middleware-user-agent": "^3.972.5", + "@aws-sdk/middleware-user-agent": "^3.972.6", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/types": "^3.973.1", - "@aws-sdk/util-endpoints": "3.980.0", + "@aws-sdk/util-endpoints": "3.982.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", - "@aws-sdk/util-user-agent-node": "^3.972.3", + "@aws-sdk/util-user-agent-node": "^3.972.4", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.22.0", "@smithy/fetch-http-handler": "^5.3.9", @@ -921,6 +953,22 @@ "node": ">=20.0.0" } }, + "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/util-endpoints": { + "version": "3.982.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.982.0.tgz", + "integrity": "sha512-M27u8FJP7O0Of9hMWX5dipp//8iglmV9jr7R8SR8RveU+Z50/8TqH68Tu6wUWBGMfXjzbVwn1INIAO5lZrlxXQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-endpoints": "^3.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/region-config-resolver": { "version": "3.972.3", "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.3.tgz", @@ -938,12 +986,12 @@ } }, "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.980.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.980.0.tgz", - "integrity": "sha512-tO2jBj+ZIVM0nEgi1SyxWtaYGpuAJdsrugmWcI3/U2MPWCYsrvKasUo0026NvJJao38wyUq9B8XTG8Xu53j/VA==", + "version": "3.983.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.983.0.tgz", + "integrity": "sha512-11FCcxI/WKRufKDdPgKPXtrhjDArhkOPb4mf66rICZUnPHlD8Cb7cjZZS/eFC+iuwoHBosrxo0hYsvK3s7DxGw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-sdk-s3": "^3.972.5", + "@aws-sdk/middleware-sdk-s3": "^3.972.6", "@aws-sdk/types": "^3.973.1", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", @@ -955,13 +1003,13 @@ } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.980.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.980.0.tgz", - "integrity": "sha512-1nFileg1wAgDmieRoj9dOawgr2hhlh7xdvcH57b1NnqfPaVlcqVJyPc6k3TLDUFPY69eEwNxdGue/0wIz58vjA==", + "version": "3.982.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.982.0.tgz", + "integrity": "sha512-v3M0KYp2TVHYHNBT7jHD9lLTWAdS9CaWJ2jboRKt0WAB65bA7iUEpR+k4VqKYtpQN4+8kKSc4w+K6kUNZkHKQw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.5", - "@aws-sdk/nested-clients": "3.980.0", + "@aws-sdk/core": "^3.973.6", + "@aws-sdk/nested-clients": "3.982.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", @@ -998,9 +1046,9 @@ } }, "node_modules/@aws-sdk/util-endpoints": { - "version": "3.980.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.980.0.tgz", - "integrity": "sha512-AjKBNEc+rjOZQE1HwcD9aCELqg1GmUj1rtICKuY8cgwB73xJ4U/kNyqKKpN2k9emGqlfDY2D8itIp/vDc6OKpw==", + "version": "3.983.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.983.0.tgz", + "integrity": "sha512-t/VbL2X3gvDEjC4gdySOeFFOZGQEBKwa23pRHeB7hBLBZ119BB/2OEFtTFWKyp3bnMQgxpeVeGS7/hxk6wpKJw==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "^3.973.1", @@ -1038,12 +1086,12 @@ } }, "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.972.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.972.3.tgz", - "integrity": "sha512-gqG+02/lXQtO0j3US6EVnxtwwoXQC5l2qkhLCrqUrqdtcQxV7FDMbm9wLjKqoronSHyELGTjbFKK/xV5q1bZNA==", + "version": "3.972.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.972.4.tgz", + "integrity": "sha512-3WFCBLiM8QiHDfosQq3Py+lIMgWlFWwFQliUHUqwEiRqLnKyhgbU3AKa7AWJF7lW2Oc/2kFNY4MlAYVnVc0i8A==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-user-agent": "^3.972.5", + "@aws-sdk/middleware-user-agent": "^3.972.6", "@aws-sdk/types": "^3.973.1", "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", @@ -1062,13 +1110,13 @@ } }, "node_modules/@aws-sdk/xml-builder": { - "version": "3.972.2", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.2.tgz", - "integrity": "sha512-jGOOV/bV1DhkkUhHiZ3/1GZ67cZyOXaDb7d1rYD6ZiXf5V9tBNOcgqXwRRPvrCbYaFRa1pPMFb3ZjqjWpR3YfA==", + "version": "3.972.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.4.tgz", + "integrity": "sha512-0zJ05ANfYqI6+rGqj8samZBFod0dPPousBjLEqg8WdxSgbMAkRgLyn81lP215Do0rFJ/17LIXwr7q0yK24mP6Q==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.12.0", - "fast-xml-parser": "5.2.5", + "fast-xml-parser": "5.3.4", "tslib": "^2.6.2" }, "engines": { @@ -3235,9 +3283,9 @@ } }, "node_modules/@isaacs/brace-expansion": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", - "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz", + "integrity": "sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==", "license": "MIT", "dependencies": { "@isaacs/balanced-match": "^4.0.1" @@ -4629,9 +4677,9 @@ } }, "node_modules/@posthog/core": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/@posthog/core/-/core-1.17.0.tgz", - "integrity": "sha512-8pDNL+/u9ojzXloA5wILVDXBCV5daJ7w2ipCALQlEEZmL752cCKhRpbyiHn3tjKXh3Hy6aOboJneYa1JdlVHrQ==", + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/@posthog/core/-/core-1.20.0.tgz", + "integrity": "sha512-e/F20we0t6bPMuDOVOe53f908s23vuKEpFKNXmZcx4bSYsPkjRN49akIIHU621HBJdcsFx537vhJYKZxu8uS9w==", "license": "MIT", "dependencies": { "cross-spawn": "^7.0.6" @@ -6936,9 +6984,9 @@ } }, "node_modules/@react-email/components": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@react-email/components/-/components-1.0.6.tgz", - "integrity": "sha512-3GwOeq+5yyiAcwSf7TnHi/HWKn22lXbwxQmkkAviSwZLlhsRVxvmWqRxvUVfQk/HclDUG+62+sGz9qjfb2Uxjw==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@react-email/components/-/components-1.0.7.tgz", + "integrity": "sha512-mY+v4C1SMaGOKuKp0QWDQLGK+3fvH06ZE10EVavv+T6tQneDHq9cpQ9NdCrvuO1nWZnWrA/0tRpvyqyF0uo93w==", "license": "MIT", "dependencies": { "@react-email/body": "0.2.1", @@ -6959,7 +7007,7 @@ "@react-email/render": "2.0.4", "@react-email/row": "0.0.13", "@react-email/section": "0.0.17", - "@react-email/tailwind": "2.0.3", + "@react-email/tailwind": "2.0.4", "@react-email/text": "0.1.6" }, "engines": { @@ -7134,9 +7182,9 @@ } }, "node_modules/@react-email/tailwind": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@react-email/tailwind/-/tailwind-2.0.3.tgz", - "integrity": "sha512-URXb/T2WS4RlNGM5QwekYnivuiVUcU87H0y5sqLl6/Oi3bMmgL0Bmw/W9GeJylC+876Vw+E6NkE0uRiUFIQwGg==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@react-email/tailwind/-/tailwind-2.0.4.tgz", + "integrity": "sha512-cDp8Ss6LJKI8zBLKE+tsXFurn6I2nnQNg1qqjfZuNPNoToN1Uyx3egW0bwSVk1JjrNWx/Xnme7ZxvNLRrU9K0Q==", "license": "MIT", "dependencies": { "tailwindcss": "^4.1.18" @@ -9178,9 +9226,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.2.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.0.tgz", - "integrity": "sha512-DZ8VwRFUNzuqJ5khrvwMXHmvPe+zGayJhr2CDNiKB1WBE1ST8Djl00D0IC4vvNmHMdj6DlbYRIaFE7WHjlDl5w==", + "version": "25.2.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.1.tgz", + "integrity": "sha512-CPrnr8voK8vC6eEtyRzvMpgp3VyVRhgclonE7qYi6P9sXwYb59ucfrnmFBTaP0yUi8Gk4yZg/LlTJULGxvTNsg==", "devOptional": true, "license": "MIT", "peer": true, @@ -9233,9 +9281,9 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "19.2.10", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.10.tgz", - "integrity": "sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==", + "version": "19.2.13", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.13.tgz", + "integrity": "sha512-KkiJeU6VbYbUOp5ITMIc7kBfqlYkKA5KhEHVrGMmUUMt7NeaZg65ojdPk+FtNrBAOXNVM5QM72jnADjM+XVRAQ==", "devOptional": true, "license": "MIT", "peer": true, @@ -13713,9 +13761,9 @@ "license": "BSD-3-Clause" }, "node_modules/fast-xml-parser": { - "version": "5.2.5", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", - "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.3.4.tgz", + "integrity": "sha512-EFd6afGmXlCx8H8WTZHhAoDaWaGyuIBoZJ2mknrNxug+aZKjkp0a0dlars9Izl+jF+7Gu1/5f/2h68cQpe0IiA==", "funding": [ { "type": "github", @@ -14183,12 +14231,12 @@ "license": "MIT" }, "node_modules/glob": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.0.tgz", - "integrity": "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==", + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.1.tgz", + "integrity": "sha512-B7U/vJpE3DkJ5WXTgTpTRN63uV42DseiXXKMwG14LQBXmsdeIoHAPbU/MEo6II0k5ED74uc2ZGTC6MwHFQhF6w==", "license": "BlueOak-1.0.0", "dependencies": { - "minimatch": "^10.1.1", + "minimatch": "^10.1.2", "minipass": "^7.1.2", "path-scurry": "^2.0.0" }, @@ -14212,12 +14260,12 @@ } }, "node_modules/glob/node_modules/minimatch": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", - "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.2.tgz", + "integrity": "sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw==", "license": "BlueOak-1.0.0", "dependencies": { - "@isaacs/brace-expansion": "^5.0.0" + "@isaacs/brace-expansion": "^5.0.1" }, "engines": { "node": "20 || >=22" @@ -16339,9 +16387,9 @@ "license": "MIT" }, "node_modules/nodemailer": { - "version": "7.0.13", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.13.tgz", - "integrity": "sha512-PNDFSJdP+KFgdsG3ZzMXCgquO7I6McjY2vlqILjtJd0hy8wEvtugS9xKRF2NWlPNGxvLCXlTNIae4serI7dinw==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-8.0.0.tgz", + "integrity": "sha512-xvVJf/f0bzmNpnRIbhCp/IKxaHgJ6QynvUbLXzzMRPG3LDQr5oXkYuw4uDFyFYs8cge8agwwrJAXZsd4hhMquw==", "license": "MIT-0", "engines": { "node": ">=6.0.0" @@ -16357,162 +16405,6 @@ "node": ">=0.10.0" } }, - "node_modules/npm": { - "version": "11.8.0", - "resolved": "https://registry.npmjs.org/npm/-/npm-11.8.0.tgz", - "integrity": "sha512-n19sJeW+RGKdkHo8SCc5xhSwkKhQUFfZaFzSc+EsYXLjSqIV0tl72aDYQVuzVvfrbysGwdaQsNLNy58J10EBSQ==", - "bundleDependencies": [ - "@isaacs/string-locale-compare", - "@npmcli/arborist", - "@npmcli/config", - "@npmcli/fs", - "@npmcli/map-workspaces", - "@npmcli/metavuln-calculator", - "@npmcli/package-json", - "@npmcli/promise-spawn", - "@npmcli/redact", - "@npmcli/run-script", - "@sigstore/tuf", - "abbrev", - "archy", - "cacache", - "chalk", - "ci-info", - "cli-columns", - "fastest-levenshtein", - "fs-minipass", - "glob", - "graceful-fs", - "hosted-git-info", - "ini", - "init-package-json", - "is-cidr", - "json-parse-even-better-errors", - "libnpmaccess", - "libnpmdiff", - "libnpmexec", - "libnpmfund", - "libnpmorg", - "libnpmpack", - "libnpmpublish", - "libnpmsearch", - "libnpmteam", - "libnpmversion", - "make-fetch-happen", - "minimatch", - "minipass", - "minipass-pipeline", - "ms", - "node-gyp", - "nopt", - "npm-audit-report", - "npm-install-checks", - "npm-package-arg", - "npm-pick-manifest", - "npm-profile", - "npm-registry-fetch", - "npm-user-validate", - "p-map", - "pacote", - "parse-conflict-json", - "proc-log", - "qrcode-terminal", - "read", - "semver", - "spdx-expression-parse", - "ssri", - "supports-color", - "tar", - "text-table", - "tiny-relative-date", - "treeverse", - "validate-npm-package-name", - "which" - ], - "license": "Artistic-2.0", - "workspaces": [ - "docs", - "smoke-tests", - "mock-globals", - "mock-registry", - "workspaces/*" - ], - "dependencies": { - "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/arborist": "^9.1.10", - "@npmcli/config": "^10.5.0", - "@npmcli/fs": "^5.0.0", - "@npmcli/map-workspaces": "^5.0.3", - "@npmcli/metavuln-calculator": "^9.0.3", - "@npmcli/package-json": "^7.0.4", - "@npmcli/promise-spawn": "^9.0.1", - "@npmcli/redact": "^4.0.0", - "@npmcli/run-script": "^10.0.3", - "@sigstore/tuf": "^4.0.1", - "abbrev": "^4.0.0", - "archy": "~1.0.0", - "cacache": "^20.0.3", - "chalk": "^5.6.2", - "ci-info": "^4.3.1", - "cli-columns": "^4.0.0", - "fastest-levenshtein": "^1.0.16", - "fs-minipass": "^3.0.3", - "glob": "^13.0.0", - "graceful-fs": "^4.2.11", - "hosted-git-info": "^9.0.2", - "ini": "^6.0.0", - "init-package-json": "^8.2.4", - "is-cidr": "^6.0.1", - "json-parse-even-better-errors": "^5.0.0", - "libnpmaccess": "^10.0.3", - "libnpmdiff": "^8.0.13", - "libnpmexec": "^10.1.12", - "libnpmfund": "^7.0.13", - "libnpmorg": "^8.0.1", - "libnpmpack": "^9.0.13", - "libnpmpublish": "^11.1.3", - "libnpmsearch": "^9.0.1", - "libnpmteam": "^8.0.2", - "libnpmversion": "^8.0.3", - "make-fetch-happen": "^15.0.3", - "minimatch": "^10.1.1", - "minipass": "^7.1.1", - "minipass-pipeline": "^1.2.4", - "ms": "^2.1.2", - "node-gyp": "^12.1.0", - "nopt": "^9.0.0", - "npm-audit-report": "^7.0.0", - "npm-install-checks": "^8.0.0", - "npm-package-arg": "^13.0.2", - "npm-pick-manifest": "^11.0.3", - "npm-profile": "^12.0.1", - "npm-registry-fetch": "^19.1.1", - "npm-user-validate": "^4.0.0", - "p-map": "^7.0.4", - "pacote": "^21.0.4", - "parse-conflict-json": "^5.0.1", - "proc-log": "^6.1.0", - "qrcode-terminal": "^0.12.0", - "read": "^5.0.1", - "semver": "^7.7.3", - "spdx-expression-parse": "^4.0.0", - "ssri": "^13.0.0", - "supports-color": "^10.2.2", - "tar": "^7.5.4", - "text-table": "~0.2.0", - "tiny-relative-date": "^2.0.2", - "treeverse": "^3.0.0", - "validate-npm-package-name": "^7.0.2", - "which": "^6.0.0" - }, - "bin": { - "npm": "bin/npm-cli.js", - "npx": "bin/npx-cli.js" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, "node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -16526,1791 +16418,6 @@ "node": ">=8" } }, - "node_modules/npm/node_modules/@isaacs/balanced-match": { - "version": "4.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/npm/node_modules/@isaacs/brace-expansion": { - "version": "5.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "@isaacs/balanced-match": "^4.0.1" - }, - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/npm/node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.4" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/npm/node_modules/@isaacs/string-locale-compare": { - "version": "1.1.0", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/@npmcli/agent": { - "version": "4.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "agent-base": "^7.1.0", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.1", - "lru-cache": "^11.2.1", - "socks-proxy-agent": "^8.0.3" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/@npmcli/arborist": { - "version": "9.1.10", - "inBundle": true, - "license": "ISC", - "dependencies": { - "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/fs": "^5.0.0", - "@npmcli/installed-package-contents": "^4.0.0", - "@npmcli/map-workspaces": "^5.0.0", - "@npmcli/metavuln-calculator": "^9.0.2", - "@npmcli/name-from-folder": "^4.0.0", - "@npmcli/node-gyp": "^5.0.0", - "@npmcli/package-json": "^7.0.0", - "@npmcli/query": "^5.0.0", - "@npmcli/redact": "^4.0.0", - "@npmcli/run-script": "^10.0.0", - "bin-links": "^6.0.0", - "cacache": "^20.0.1", - "common-ancestor-path": "^2.0.0", - "hosted-git-info": "^9.0.0", - "json-stringify-nice": "^1.1.4", - "lru-cache": "^11.2.1", - "minimatch": "^10.0.3", - "nopt": "^9.0.0", - "npm-install-checks": "^8.0.0", - "npm-package-arg": "^13.0.0", - "npm-pick-manifest": "^11.0.1", - "npm-registry-fetch": "^19.0.0", - "pacote": "^21.0.2", - "parse-conflict-json": "^5.0.1", - "proc-log": "^6.0.0", - "proggy": "^4.0.0", - "promise-all-reject-late": "^1.0.0", - "promise-call-limit": "^3.0.1", - "semver": "^7.3.7", - "ssri": "^13.0.0", - "treeverse": "^3.0.0", - "walk-up-path": "^4.0.0" - }, - "bin": { - "arborist": "bin/index.js" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/@npmcli/config": { - "version": "10.5.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/map-workspaces": "^5.0.0", - "@npmcli/package-json": "^7.0.0", - "ci-info": "^4.0.0", - "ini": "^6.0.0", - "nopt": "^9.0.0", - "proc-log": "^6.0.0", - "semver": "^7.3.5", - "walk-up-path": "^4.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/@npmcli/fs": { - "version": "5.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/@npmcli/git": { - "version": "7.0.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/promise-spawn": "^9.0.0", - "ini": "^6.0.0", - "lru-cache": "^11.2.1", - "npm-pick-manifest": "^11.0.1", - "proc-log": "^6.0.0", - "promise-retry": "^2.0.1", - "semver": "^7.3.5", - "which": "^6.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/@npmcli/installed-package-contents": { - "version": "4.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "npm-bundled": "^5.0.0", - "npm-normalize-package-bin": "^5.0.0" - }, - "bin": { - "installed-package-contents": "bin/index.js" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/@npmcli/map-workspaces": { - "version": "5.0.3", - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/name-from-folder": "^4.0.0", - "@npmcli/package-json": "^7.0.0", - "glob": "^13.0.0", - "minimatch": "^10.0.3" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/@npmcli/metavuln-calculator": { - "version": "9.0.3", - "inBundle": true, - "license": "ISC", - "dependencies": { - "cacache": "^20.0.0", - "json-parse-even-better-errors": "^5.0.0", - "pacote": "^21.0.0", - "proc-log": "^6.0.0", - "semver": "^7.3.5" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/@npmcli/name-from-folder": { - "version": "4.0.0", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/@npmcli/node-gyp": { - "version": "5.0.0", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/@npmcli/package-json": { - "version": "7.0.4", - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/git": "^7.0.0", - "glob": "^13.0.0", - "hosted-git-info": "^9.0.0", - "json-parse-even-better-errors": "^5.0.0", - "proc-log": "^6.0.0", - "semver": "^7.5.3", - "validate-npm-package-license": "^3.0.4" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/@npmcli/promise-spawn": { - "version": "9.0.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "which": "^6.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/@npmcli/query": { - "version": "5.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/@npmcli/redact": { - "version": "4.0.0", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/@npmcli/run-script": { - "version": "10.0.3", - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/node-gyp": "^5.0.0", - "@npmcli/package-json": "^7.0.0", - "@npmcli/promise-spawn": "^9.0.0", - "node-gyp": "^12.1.0", - "proc-log": "^6.0.0", - "which": "^6.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/@sigstore/bundle": { - "version": "4.0.0", - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "@sigstore/protobuf-specs": "^0.5.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/@sigstore/core": { - "version": "3.1.0", - "inBundle": true, - "license": "Apache-2.0", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/@sigstore/protobuf-specs": { - "version": "0.5.0", - "inBundle": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/@sigstore/sign": { - "version": "4.1.0", - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "@sigstore/bundle": "^4.0.0", - "@sigstore/core": "^3.1.0", - "@sigstore/protobuf-specs": "^0.5.0", - "make-fetch-happen": "^15.0.3", - "proc-log": "^6.1.0", - "promise-retry": "^2.0.1" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/@sigstore/tuf": { - "version": "4.0.1", - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "@sigstore/protobuf-specs": "^0.5.0", - "tuf-js": "^4.1.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/@sigstore/verify": { - "version": "3.1.0", - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "@sigstore/bundle": "^4.0.0", - "@sigstore/core": "^3.1.0", - "@sigstore/protobuf-specs": "^0.5.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/@tufjs/canonical-json": { - "version": "2.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/@tufjs/models": { - "version": "4.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "@tufjs/canonical-json": "2.0.0", - "minimatch": "^10.1.1" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/abbrev": { - "version": "4.0.0", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/agent-base": { - "version": "7.1.4", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "node_modules/npm/node_modules/ansi-regex": { - "version": "5.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/aproba": { - "version": "2.1.0", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/archy": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/bin-links": { - "version": "6.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "cmd-shim": "^8.0.0", - "npm-normalize-package-bin": "^5.0.0", - "proc-log": "^6.0.0", - "read-cmd-shim": "^6.0.0", - "write-file-atomic": "^7.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/binary-extensions": { - "version": "3.1.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=18.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm/node_modules/cacache": { - "version": "20.0.3", - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/fs": "^5.0.0", - "fs-minipass": "^3.0.0", - "glob": "^13.0.0", - "lru-cache": "^11.1.0", - "minipass": "^7.0.3", - "minipass-collect": "^2.0.1", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "p-map": "^7.0.2", - "ssri": "^13.0.0", - "unique-filename": "^5.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/chalk": { - "version": "5.6.2", - "inBundle": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/npm/node_modules/chownr": { - "version": "3.0.0", - "inBundle": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/npm/node_modules/ci-info": { - "version": "4.3.1", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/cidr-regex": { - "version": "5.0.1", - "inBundle": true, - "license": "BSD-2-Clause", - "dependencies": { - "ip-regex": "5.0.0" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/npm/node_modules/cli-columns": { - "version": "4.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/npm/node_modules/cmd-shim": { - "version": "8.0.0", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/common-ancestor-path": { - "version": "2.0.0", - "inBundle": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">= 18" - } - }, - "node_modules/npm/node_modules/cssesc": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/debug": { - "version": "4.4.3", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/npm/node_modules/diff": { - "version": "8.0.3", - "inBundle": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/npm/node_modules/emoji-regex": { - "version": "8.0.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/encoding": { - "version": "0.1.13", - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "iconv-lite": "^0.6.2" - } - }, - "node_modules/npm/node_modules/env-paths": { - "version": "2.2.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/npm/node_modules/err-code": { - "version": "2.0.3", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/exponential-backoff": { - "version": "3.1.3", - "inBundle": true, - "license": "Apache-2.0" - }, - "node_modules/npm/node_modules/fastest-levenshtein": { - "version": "1.0.16", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 4.9.1" - } - }, - "node_modules/npm/node_modules/fs-minipass": { - "version": "3.0.3", - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.3" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/glob": { - "version": "13.0.0", - "inBundle": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "minimatch": "^10.1.1", - "minipass": "^7.1.2", - "path-scurry": "^2.0.0" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/graceful-fs": { - "version": "4.2.11", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/hosted-git-info": { - "version": "9.0.2", - "inBundle": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^11.1.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/http-cache-semantics": { - "version": "4.2.0", - "inBundle": true, - "license": "BSD-2-Clause" - }, - "node_modules/npm/node_modules/http-proxy-agent": { - "version": "7.0.2", - "inBundle": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/npm/node_modules/https-proxy-agent": { - "version": "7.0.6", - "inBundle": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/npm/node_modules/iconv-lite": { - "version": "0.6.3", - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm/node_modules/ignore-walk": { - "version": "8.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "minimatch": "^10.0.3" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/imurmurhash": { - "version": "0.1.4", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/npm/node_modules/ini": { - "version": "6.0.0", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/init-package-json": { - "version": "8.2.4", - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/package-json": "^7.0.0", - "npm-package-arg": "^13.0.0", - "promzard": "^3.0.1", - "read": "^5.0.1", - "semver": "^7.7.2", - "validate-npm-package-license": "^3.0.4", - "validate-npm-package-name": "^7.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/ip-address": { - "version": "10.1.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/npm/node_modules/ip-regex": { - "version": "5.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm/node_modules/is-cidr": { - "version": "6.0.1", - "inBundle": true, - "license": "BSD-2-Clause", - "dependencies": { - "cidr-regex": "5.0.1" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/npm/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/isexe": { - "version": "3.1.1", - "inBundle": true, - "license": "ISC", - "engines": { - "node": ">=16" - } - }, - "node_modules/npm/node_modules/json-parse-even-better-errors": { - "version": "5.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/json-stringify-nice": { - "version": "1.1.4", - "inBundle": true, - "license": "ISC", - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/jsonparse": { - "version": "1.3.1", - "engines": [ - "node >= 0.2.0" - ], - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/just-diff": { - "version": "6.0.2", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/just-diff-apply": { - "version": "5.5.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/libnpmaccess": { - "version": "10.0.3", - "inBundle": true, - "license": "ISC", - "dependencies": { - "npm-package-arg": "^13.0.0", - "npm-registry-fetch": "^19.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/libnpmdiff": { - "version": "8.0.13", - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/arborist": "^9.1.10", - "@npmcli/installed-package-contents": "^4.0.0", - "binary-extensions": "^3.0.0", - "diff": "^8.0.2", - "minimatch": "^10.0.3", - "npm-package-arg": "^13.0.0", - "pacote": "^21.0.2", - "tar": "^7.5.1" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/libnpmexec": { - "version": "10.1.12", - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/arborist": "^9.1.10", - "@npmcli/package-json": "^7.0.0", - "@npmcli/run-script": "^10.0.0", - "ci-info": "^4.0.0", - "npm-package-arg": "^13.0.0", - "pacote": "^21.0.2", - "proc-log": "^6.0.0", - "promise-retry": "^2.0.1", - "read": "^5.0.1", - "semver": "^7.3.7", - "signal-exit": "^4.1.0", - "walk-up-path": "^4.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/libnpmfund": { - "version": "7.0.13", - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/arborist": "^9.1.10" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/libnpmorg": { - "version": "8.0.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "aproba": "^2.0.0", - "npm-registry-fetch": "^19.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/libnpmpack": { - "version": "9.0.13", - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/arborist": "^9.1.10", - "@npmcli/run-script": "^10.0.0", - "npm-package-arg": "^13.0.0", - "pacote": "^21.0.2" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/libnpmpublish": { - "version": "11.1.3", - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/package-json": "^7.0.0", - "ci-info": "^4.0.0", - "npm-package-arg": "^13.0.0", - "npm-registry-fetch": "^19.0.0", - "proc-log": "^6.0.0", - "semver": "^7.3.7", - "sigstore": "^4.0.0", - "ssri": "^13.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/libnpmsearch": { - "version": "9.0.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "npm-registry-fetch": "^19.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/libnpmteam": { - "version": "8.0.2", - "inBundle": true, - "license": "ISC", - "dependencies": { - "aproba": "^2.0.0", - "npm-registry-fetch": "^19.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/libnpmversion": { - "version": "8.0.3", - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/git": "^7.0.0", - "@npmcli/run-script": "^10.0.0", - "json-parse-even-better-errors": "^5.0.0", - "proc-log": "^6.0.0", - "semver": "^7.3.7" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/lru-cache": { - "version": "11.2.4", - "inBundle": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/npm/node_modules/make-fetch-happen": { - "version": "15.0.3", - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/agent": "^4.0.0", - "cacache": "^20.0.1", - "http-cache-semantics": "^4.1.1", - "minipass": "^7.0.2", - "minipass-fetch": "^5.0.0", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^1.0.0", - "proc-log": "^6.0.0", - "promise-retry": "^2.0.1", - "ssri": "^13.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/minimatch": { - "version": "10.1.1", - "inBundle": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/brace-expansion": "^5.0.0" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/minipass": { - "version": "7.1.2", - "inBundle": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/npm/node_modules/minipass-collect": { - "version": "2.0.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.3" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/npm/node_modules/minipass-fetch": { - "version": "5.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "minipass": "^7.0.3", - "minipass-sized": "^1.0.3", - "minizlib": "^3.0.1" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - }, - "optionalDependencies": { - "encoding": "^0.1.13" - } - }, - "node_modules/npm/node_modules/minipass-flush": { - "version": "1.0.5", - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/npm/node_modules/minipass-flush/node_modules/minipass": { - "version": "3.3.6", - "inBundle": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/minipass-pipeline": { - "version": "1.2.4", - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/minipass-pipeline/node_modules/minipass": { - "version": "3.3.6", - "inBundle": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/minipass-sized": { - "version": "1.0.3", - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/minipass-sized/node_modules/minipass": { - "version": "3.3.6", - "inBundle": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/minizlib": { - "version": "3.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "minipass": "^7.1.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/npm/node_modules/ms": { - "version": "2.1.3", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/mute-stream": { - "version": "3.0.0", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/negotiator": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/npm/node_modules/node-gyp": { - "version": "12.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "env-paths": "^2.2.0", - "exponential-backoff": "^3.1.1", - "graceful-fs": "^4.2.6", - "make-fetch-happen": "^15.0.0", - "nopt": "^9.0.0", - "proc-log": "^6.0.0", - "semver": "^7.3.5", - "tar": "^7.5.2", - "tinyglobby": "^0.2.12", - "which": "^6.0.0" - }, - "bin": { - "node-gyp": "bin/node-gyp.js" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/nopt": { - "version": "9.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "abbrev": "^4.0.0" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/npm-audit-report": { - "version": "7.0.0", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/npm-bundled": { - "version": "5.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "npm-normalize-package-bin": "^5.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/npm-install-checks": { - "version": "8.0.0", - "inBundle": true, - "license": "BSD-2-Clause", - "dependencies": { - "semver": "^7.1.1" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/npm-normalize-package-bin": { - "version": "5.0.0", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/npm-package-arg": { - "version": "13.0.2", - "inBundle": true, - "license": "ISC", - "dependencies": { - "hosted-git-info": "^9.0.0", - "proc-log": "^6.0.0", - "semver": "^7.3.5", - "validate-npm-package-name": "^7.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/npm-packlist": { - "version": "10.0.3", - "inBundle": true, - "license": "ISC", - "dependencies": { - "ignore-walk": "^8.0.0", - "proc-log": "^6.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/npm-pick-manifest": { - "version": "11.0.3", - "inBundle": true, - "license": "ISC", - "dependencies": { - "npm-install-checks": "^8.0.0", - "npm-normalize-package-bin": "^5.0.0", - "npm-package-arg": "^13.0.0", - "semver": "^7.3.5" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/npm-profile": { - "version": "12.0.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "npm-registry-fetch": "^19.0.0", - "proc-log": "^6.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/npm-registry-fetch": { - "version": "19.1.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/redact": "^4.0.0", - "jsonparse": "^1.3.1", - "make-fetch-happen": "^15.0.0", - "minipass": "^7.0.2", - "minipass-fetch": "^5.0.0", - "minizlib": "^3.0.1", - "npm-package-arg": "^13.0.0", - "proc-log": "^6.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/npm-user-validate": { - "version": "4.0.0", - "inBundle": true, - "license": "BSD-2-Clause", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/p-map": { - "version": "7.0.4", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm/node_modules/pacote": { - "version": "21.0.4", - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/git": "^7.0.0", - "@npmcli/installed-package-contents": "^4.0.0", - "@npmcli/package-json": "^7.0.0", - "@npmcli/promise-spawn": "^9.0.0", - "@npmcli/run-script": "^10.0.0", - "cacache": "^20.0.0", - "fs-minipass": "^3.0.0", - "minipass": "^7.0.2", - "npm-package-arg": "^13.0.0", - "npm-packlist": "^10.0.1", - "npm-pick-manifest": "^11.0.1", - "npm-registry-fetch": "^19.0.0", - "proc-log": "^6.0.0", - "promise-retry": "^2.0.1", - "sigstore": "^4.0.0", - "ssri": "^13.0.0", - "tar": "^7.4.3" - }, - "bin": { - "pacote": "bin/index.js" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/parse-conflict-json": { - "version": "5.0.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "json-parse-even-better-errors": "^5.0.0", - "just-diff": "^6.0.0", - "just-diff-apply": "^5.2.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/path-scurry": { - "version": "2.0.1", - "inBundle": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/proc-log": { - "version": "6.1.0", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/proggy": { - "version": "4.0.0", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/promise-all-reject-late": { - "version": "1.0.1", - "inBundle": true, - "license": "ISC", - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/promise-call-limit": { - "version": "3.0.2", - "inBundle": true, - "license": "ISC", - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/promise-retry": { - "version": "2.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "err-code": "^2.0.2", - "retry": "^0.12.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/npm/node_modules/promzard": { - "version": "3.0.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "read": "^5.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/qrcode-terminal": { - "version": "0.12.0", - "inBundle": true, - "bin": { - "qrcode-terminal": "bin/qrcode-terminal.js" - } - }, - "node_modules/npm/node_modules/read": { - "version": "5.0.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "mute-stream": "^3.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/read-cmd-shim": { - "version": "6.0.0", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/retry": { - "version": "0.12.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/npm/node_modules/safer-buffer": { - "version": "2.1.2", - "inBundle": true, - "license": "MIT", - "optional": true - }, - "node_modules/npm/node_modules/semver": { - "version": "7.7.3", - "inBundle": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/npm/node_modules/signal-exit": { - "version": "4.1.0", - "inBundle": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/sigstore": { - "version": "4.1.0", - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "@sigstore/bundle": "^4.0.0", - "@sigstore/core": "^3.1.0", - "@sigstore/protobuf-specs": "^0.5.0", - "@sigstore/sign": "^4.1.0", - "@sigstore/tuf": "^4.0.1", - "@sigstore/verify": "^3.1.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/smart-buffer": { - "version": "4.2.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/npm/node_modules/socks": { - "version": "2.8.7", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ip-address": "^10.0.1", - "smart-buffer": "^4.2.0" - }, - "engines": { - "node": ">= 10.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/npm/node_modules/socks-proxy-agent": { - "version": "8.0.5", - "inBundle": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "socks": "^2.8.3" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/npm/node_modules/spdx-correct": { - "version": "3.2.0", - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/npm/node_modules/spdx-correct/node_modules/spdx-expression-parse": { - "version": "3.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/npm/node_modules/spdx-exceptions": { - "version": "2.5.0", - "inBundle": true, - "license": "CC-BY-3.0" - }, - "node_modules/npm/node_modules/spdx-expression-parse": { - "version": "4.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/npm/node_modules/spdx-license-ids": { - "version": "3.0.22", - "inBundle": true, - "license": "CC0-1.0" - }, - "node_modules/npm/node_modules/ssri": { - "version": "13.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.3" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/string-width": { - "version": "4.2.3", - "inBundle": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/strip-ansi": { - "version": "6.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/supports-color": { - "version": "10.2.2", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/npm/node_modules/tar": { - "version": "7.5.4", - "inBundle": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.1.0", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/npm/node_modules/tar/node_modules/yallist": { - "version": "5.0.0", - "inBundle": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/npm/node_modules/text-table": { - "version": "0.2.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/tiny-relative-date": { - "version": "2.0.2", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/tinyglobby": { - "version": "0.2.15", - "inBundle": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/npm/node_modules/tinyglobby/node_modules/fdir": { - "version": "6.5.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/npm/node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "inBundle": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/npm/node_modules/treeverse": { - "version": "3.0.0", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/tuf-js": { - "version": "4.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "@tufjs/models": "4.1.0", - "debug": "^4.4.3", - "make-fetch-happen": "^15.0.1" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/unique-filename": { - "version": "5.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "unique-slug": "^6.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/unique-slug": { - "version": "6.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/util-deprecate": { - "version": "1.0.2", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/validate-npm-package-license": { - "version": "3.0.4", - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "node_modules/npm/node_modules/validate-npm-package-license/node_modules/spdx-expression-parse": { - "version": "3.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/npm/node_modules/validate-npm-package-name": { - "version": "7.0.2", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/walk-up-path": { - "version": "4.0.0", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/npm/node_modules/which": { - "version": "6.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "isexe": "^3.1.1" - }, - "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/write-file-atomic": { - "version": "7.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/yallist": { - "version": "4.0.0", - "inBundle": true, - "license": "ISC" - }, "node_modules/nprogress": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/nprogress/-/nprogress-0.2.0.tgz", @@ -19226,12 +17333,12 @@ } }, "node_modules/posthog-node": { - "version": "5.24.7", - "resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-5.24.7.tgz", - "integrity": "sha512-IJ0Zj+v+eg/JQMZ75n0Hcp4NzuQzWcZjqFjcUQs6RhW2l5FiQIq09sKJMleXX33hYxD6sfjFsDTqugJlgeAohg==", + "version": "5.24.10", + "resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-5.24.10.tgz", + "integrity": "sha512-C4ueZUrifTJMDFngybSWQ+GthcqCqPiCcGg5qnjoh+f6ie3+tdhFROqqshjttpQ6Q4DPM40USPTmU/UBYqgsbA==", "license": "MIT", "dependencies": { - "@posthog/core": "1.17.0" + "@posthog/core": "1.20.0" }, "engines": { "node": "^20.20.0 || >=22.22.0" @@ -19565,9 +17672,9 @@ } }, "node_modules/react-email": { - "version": "5.2.5", - "resolved": "https://registry.npmjs.org/react-email/-/react-email-5.2.5.tgz", - "integrity": "sha512-YaCp5n/0czviN4lFndsYongiI0IJOMFtFoRVIPJc9+WPJejJEvzJO94r31p3Cz9swDuV0RhEhH1W0lJFAXntHA==", + "version": "5.2.7", + "resolved": "https://registry.npmjs.org/react-email/-/react-email-5.2.7.tgz", + "integrity": "sha512-qTRqN+Ftvc6k8COSDM3IROmimbBWMykWFWxk4FKDKjoLw9bMfwavXJSeACmkwoiv+7jws/Pm1xNf9eoR7blt8g==", "dev": true, "license": "MIT", "dependencies": { @@ -20861,9 +18968,9 @@ } }, "node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -21695,9 +19802,9 @@ } }, "node_modules/stripe": { - "version": "20.3.0", - "resolved": "https://registry.npmjs.org/stripe/-/stripe-20.3.0.tgz", - "integrity": "sha512-DYzcmV1MfYhycr1GwjCjeQVYk9Gu8dpxyTlu7qeDCsuguug7oUTxPsUQuZeSf/OPzK7pofqobvOKVqAwlpgf/Q==", + "version": "20.3.1", + "resolved": "https://registry.npmjs.org/stripe/-/stripe-20.3.1.tgz", + "integrity": "sha512-k990yOT5G5rhX3XluRPw5Y8RLdJDW4dzQ29wWT66piHrbnM2KyamJ1dKgPsw4HzGHRWjDiSSdcI2WdxQUPV3aQ==", "license": "MIT", "engines": { "node": ">=16" diff --git a/package.json b/package.json index 61b2deaf..dda4ebf2 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ }, "dependencies": { "@asteasolutions/zod-to-openapi": "8.4.0", - "@aws-sdk/client-s3": "3.980.0", + "@aws-sdk/client-s3": "3.983.0", "@faker-js/faker": "10.2.0", "@headlessui/react": "2.2.9", "@hookform/resolvers": "5.2.2", @@ -63,9 +63,9 @@ "@radix-ui/react-tabs": "1.1.13", "@radix-ui/react-toast": "1.2.15", "@radix-ui/react-tooltip": "1.2.8", - "@react-email/components": "1.0.6", + "@react-email/components": "1.0.7", "@react-email/render": "2.0.4", - "@react-email/tailwind": "2.0.3", + "@react-email/tailwind": "2.0.4", "@simplewebauthn/browser": "13.2.2", "@simplewebauthn/server": "13.2.2", "@tailwindcss/forms": "0.5.11", @@ -79,7 +79,7 @@ "clsx": "2.1.1", "cmdk": "1.1.1", "cookie-parser": "1.4.7", - "cors": "2.8.5", + "cors": "2.8.6", "crypto-js": "4.2.0", "d3": "7.9.0", "date-fns": "4.1.0", @@ -88,7 +88,7 @@ "eslint-config-next": "16.1.6", "express": "5.2.1", "express-rate-limit": "8.2.1", - "glob": "13.0.0", + "glob": "13.0.1", "helmet": "8.1.0", "http-errors": "2.0.1", "input-otp": "1.4.2", @@ -104,10 +104,10 @@ "next-themes": "0.4.6", "nextjs-toploader": "3.9.17", "node-cache": "5.1.2", - "nodemailer": "7.0.11", + "nodemailer": "8.0.0", "oslo": "1.2.1", "pg": "8.18.0", - "posthog-node": "5.24.7", + "posthog-node": "5.24.10", "qrcode.react": "4.2.0", "react": "19.2.4", "react-day-picker": "9.13.0", @@ -118,8 +118,8 @@ "recharts": "2.15.4", "reodotdev": "1.0.0", "resend": "6.9.1", - "semver": "7.7.3", - "stripe": "20.3.0", + "semver": "7.7.4", + "stripe": "20.3.1", "swagger-ui-express": "5.0.1", "tailwind-merge": "3.4.0", "topojson-client": "3.1.0", @@ -149,11 +149,11 @@ "@types/express-session": "1.18.2", "@types/jmespath": "0.15.2", "@types/jsonwebtoken": "9.0.10", - "@types/node": "25.2.0", + "@types/node": "25.2.1", "@types/nodemailer": "7.0.9", "@types/nprogress": "0.2.3", "@types/pg": "8.16.0", - "@types/react": "19.2.10", + "@types/react": "19.2.13", "@types/react-dom": "19.2.3", "@types/semver": "7.7.1", "@types/swagger-ui-express": "4.1.8", @@ -167,7 +167,7 @@ "esbuild-node-externals": "1.20.1", "postcss": "8.5.6", "prettier": "3.8.1", - "react-email": "5.2.5", + "react-email": "5.2.7", "tailwindcss": "4.1.18", "tsc-alias": "1.8.16", "tsx": "4.21.0", From 0f4d1d2a741e472a1ff4aa9ccc1382a3f0f75c36 Mon Sep 17 00:00:00 2001 From: Lokowitz Date: Thu, 5 Feb 2026 19:46:57 +0000 Subject: [PATCH 19/81] add preview server --- package-lock.json | 268 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 3 +- 2 files changed, 270 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 5aee9af2..b3841ea3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -111,6 +111,7 @@ "devDependencies": { "@dotenvx/dotenvx": "1.52.0", "@esbuild-plugins/tsconfig-paths": "0.1.2", + "@react-email/preview-server": "5.2.7", "@tailwindcss/postcss": "4.1.18", "@tanstack/react-query-devtools": "5.91.3", "@types/better-sqlite3": "7.6.13", @@ -2856,6 +2857,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -2878,6 +2880,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -2900,6 +2903,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2916,6 +2920,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2932,6 +2937,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2948,6 +2954,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2964,6 +2971,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2980,6 +2988,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2996,6 +3005,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -3012,6 +3022,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -3028,6 +3039,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -3044,6 +3056,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -3066,6 +3079,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -3088,6 +3102,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -3110,6 +3125,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -3132,6 +3148,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -3154,6 +3171,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -3176,6 +3194,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -3198,6 +3217,7 @@ "cpu": [ "wasm32" ], + "dev": true, "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", "optional": true, "dependencies": { @@ -3217,6 +3237,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ @@ -3236,6 +3257,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ @@ -3255,6 +3277,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ @@ -7140,6 +7163,251 @@ "react": "^18.0 || ^19.0 || ^19.0.0-rc" } }, + "node_modules/@react-email/preview-server": { + "version": "5.2.7", + "resolved": "https://registry.npmjs.org/@react-email/preview-server/-/preview-server-5.2.7.tgz", + "integrity": "sha512-3ueOHitbbrbtclkieghHJupwoFFNMGQABBLCTIHFmyY6Gcg2CK7drwFzTFyPd7M756SvmD0E0lTDtoRkSTqrJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "next": "16.0.11" + } + }, + "node_modules/@react-email/preview-server/node_modules/@next/env": { + "version": "16.0.11", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.0.11.tgz", + "integrity": "sha512-hULMheQaOhFK1vAoFPigXca42LguwyLILtJKPRzpY1d+og6jk0YNAQVwLGNYYhWEMd2zj4gcIWSf1yC5PffqqA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@react-email/preview-server/node_modules/@next/swc-darwin-arm64": { + "version": "16.0.11", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.0.11.tgz", + "integrity": "sha512-3G7Rx6m6tgLqkc3Ce3QY/Yrsx7nJF4ithdHfx70Jmzel8m2xpjnGRC+oB4UcCHvQwN0ZP5YsLJakwx/M0vWbSQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@react-email/preview-server/node_modules/@next/swc-darwin-x64": { + "version": "16.0.11", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.0.11.tgz", + "integrity": "sha512-poUTsYKRwuG+eApDngouEiN6AGcAMq8TAQYP8Nou7iMS7x6+q3dFhhyhgodIzTF9acsEINl4cIzMaM9XJor8kw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@react-email/preview-server/node_modules/@next/swc-linux-arm64-gnu": { + "version": "16.0.11", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.0.11.tgz", + "integrity": "sha512-Q9shvB+eLNrK/n8w+/ZTWSzbEIzJ56mP83ZVaqmHay6/Ulcn6THEId4gxfYCXmSwEG/xPAtv58FBWeZkp36XUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@react-email/preview-server/node_modules/@next/swc-linux-arm64-musl": { + "version": "16.0.11", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.0.11.tgz", + "integrity": "sha512-rq+d/a0FZHVPEh3zismoQgfVkSIEzlTbNhD4Z8bToLMszUlggAh1D1syhJ4MHkYzXRszhjS2emy0PYXz7Uwttw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@react-email/preview-server/node_modules/@next/swc-linux-x64-gnu": { + "version": "16.0.11", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.0.11.tgz", + "integrity": "sha512-82Wroterii1p15O+ZF/DDsHPuxKptR1JGK+obgbAk13vrc3B/fTJ2qOOmdeoMwAQ15gb/9mN4LQl9+IzFje76Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@react-email/preview-server/node_modules/@next/swc-linux-x64-musl": { + "version": "16.0.11", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.0.11.tgz", + "integrity": "sha512-YK9RoeZuHWBd+wHi5/7VLp6P5ZOldAjQfBjjtzcR4f14FNmwT0a3ozMMlG2txDxh53krAd5yOO601RbJxH0gCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@react-email/preview-server/node_modules/@next/swc-win32-arm64-msvc": { + "version": "16.0.11", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.0.11.tgz", + "integrity": "sha512-pcDMpSckekV8xj2SSKO8PaqaJhrmDx84zUNip0kOWsT/ERhhDpnWkr6KXMqRXVp2y5CW9pp4LwOFdtpt3rhRgw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@react-email/preview-server/node_modules/@next/swc-win32-x64-msvc": { + "version": "16.0.11", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.0.11.tgz", + "integrity": "sha512-Zzo9NLLRzBSHw9zOGpER/gdc5rofZHLjR2OIUIfoBaN2Oo5zWRl43IF5rMSX2LX7MPLTx4Ww8+5lNHAhXgitnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@react-email/preview-server/node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@react-email/preview-server/node_modules/next": { + "version": "16.0.11", + "resolved": "https://registry.npmjs.org/next/-/next-16.0.11.tgz", + "integrity": "sha512-Xlo2aFWaoypPzXr4PFLSNmxrzNptlp+hgxnG9Y2THYvHrvmXIuHUyNAWO6Q+F4rm4/bmTOukprXEyF/j4qsC2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@next/env": "16.0.11", + "@swc/helpers": "0.5.15", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.4.31", + "styled-jsx": "5.1.6" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=20.9.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "16.0.11", + "@next/swc-darwin-x64": "16.0.11", + "@next/swc-linux-arm64-gnu": "16.0.11", + "@next/swc-linux-arm64-musl": "16.0.11", + "@next/swc-linux-x64-gnu": "16.0.11", + "@next/swc-linux-x64-musl": "16.0.11", + "@next/swc-win32-arm64-msvc": "16.0.11", + "@next/swc-win32-x64-msvc": "16.0.11", + "sharp": "^0.34.4" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.51.1", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/@react-email/preview-server/node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/@react-email/render": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@react-email/render/-/render-2.0.4.tgz", diff --git a/package.json b/package.json index dda4ebf2..03b7ea17 100644 --- a/package.json +++ b/package.json @@ -138,6 +138,7 @@ "devDependencies": { "@dotenvx/dotenvx": "1.52.0", "@esbuild-plugins/tsconfig-paths": "0.1.2", + "@react-email/preview-server": "5.2.7", "@tailwindcss/postcss": "4.1.18", "@tanstack/react-query-devtools": "5.91.3", "@types/better-sqlite3": "7.6.13", @@ -148,6 +149,7 @@ "@types/express": "5.0.6", "@types/express-session": "1.18.2", "@types/jmespath": "0.15.2", + "@types/js-yaml": "4.0.9", "@types/jsonwebtoken": "9.0.10", "@types/node": "25.2.1", "@types/nodemailer": "7.0.9", @@ -160,7 +162,6 @@ "@types/topojson-client": "3.1.5", "@types/ws": "8.18.1", "@types/yargs": "17.0.35", - "@types/js-yaml": "4.0.9", "babel-plugin-react-compiler": "1.0.0", "drizzle-kit": "0.31.8", "esbuild": "0.27.2", From 6c85171091245380313f8b0964fc839f8444c40b Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Fri, 6 Feb 2026 02:42:15 +0100 Subject: [PATCH 20/81] =?UTF-8?q?=E2=9C=A8serverside=20filter+paginate=20c?= =?UTF-8?q?lient=20resources=20table?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/db/pg/schema/schema.ts | 2 +- server/routers/resource/listResources.ts | 32 ----- server/routers/site/listSites.ts | 2 +- .../siteResource/listAllSiteResourcesByOrg.ts | 127 ++++++++++++------ .../settings/resources/client/page.tsx | 33 ++--- src/components/ClientResourcesTable.tsx | 86 +++++++++--- src/components/ProxyResourcesTable.tsx | 16 +-- src/components/ui/controlled-data-table.tsx | 3 +- 8 files changed, 183 insertions(+), 118 deletions(-) diff --git a/server/db/pg/schema/schema.ts b/server/db/pg/schema/schema.ts index 98c13479..82bd80e0 100644 --- a/server/db/pg/schema/schema.ts +++ b/server/db/pg/schema/schema.ts @@ -219,7 +219,7 @@ export const siteResources = pgTable("siteResources", { .references(() => orgs.orgId, { onDelete: "cascade" }), niceId: varchar("niceId").notNull(), name: varchar("name").notNull(), - mode: varchar("mode").notNull(), // "host" | "cidr" | "port" + mode: varchar("mode").$type<"host" | "cidr">().notNull(), // "host" | "cidr" | "port" protocol: varchar("protocol"), // only for port mode proxyPort: integer("proxyPort"), // only for port mode destinationPort: integer("destinationPort"), // only for port mode diff --git a/server/routers/resource/listResources.ts b/server/routers/resource/listResources.ts index add3b2b5..26a0d613 100644 --- a/server/routers/resource/listResources.ts +++ b/server/routers/resource/listResources.ts @@ -69,38 +69,6 @@ const listResourcesSchema = z.object({ .catch(undefined) }); -// (resource fields + a single joined target) -type JoinedRow = { - resourceId: number; - niceId: string; - name: string; - ssl: boolean; - fullDomain: string | null; - passwordId: number | null; - sso: boolean; - pincodeId: number | null; - whitelist: boolean; - http: boolean; - protocol: string; - proxyPort: number | null; - enabled: boolean; - domainId: string | null; - headerAuthId: number | null; - - // total_targets: number; - // healthy_targets: number; - // unhealthy_targets: number; - // unknown_targets: number; - - // targetId: number | null; - // targetIp: string | null; - // targetPort: number | null; - // targetEnabled: boolean | null; - - // hcHealth: string | null; - // hcEnabled: boolean | null; -}; - // grouped by resource with targets[]) export type ResourceWithTargets = { resourceId: number; diff --git a/server/routers/site/listSites.ts b/server/routers/site/listSites.ts index e27a328a..c65f8d10 100644 --- a/server/routers/site/listSites.ts +++ b/server/routers/site/listSites.ts @@ -247,7 +247,7 @@ export async function listSites( const baseQuery = querySitesBase().where(conditions); // we need to add `as` so that drizzle filters the result as a subquery - const countQuery = db.$count(baseQuery.as("filtered_sites")); + const countQuery = db.$count(querySitesBase().where(conditions)); const siteListQuery = baseQuery .limit(pageSize) diff --git a/server/routers/siteResource/listAllSiteResourcesByOrg.ts b/server/routers/siteResource/listAllSiteResourcesByOrg.ts index dee1eebc..2392eb76 100644 --- a/server/routers/siteResource/listAllSiteResourcesByOrg.ts +++ b/server/routers/siteResource/listAllSiteResourcesByOrg.ts @@ -1,11 +1,11 @@ import { Request, Response, NextFunction } from "express"; import { z } from "zod"; -import { db } from "@server/db"; +import { db, resources } from "@server/db"; import { siteResources, sites, SiteResource } from "@server/db"; import response from "@server/lib/response"; import HttpCode from "@server/types/HttpCode"; import createHttpError from "http-errors"; -import { eq, and } from "drizzle-orm"; +import { eq, and, asc, ilike, or } from "drizzle-orm"; import { fromError } from "zod-validation-error"; import logger from "@server/logger"; import { OpenAPITags, registry } from "@server/openApi"; @@ -15,18 +15,22 @@ const listAllSiteResourcesByOrgParamsSchema = z.strictObject({ }); const listAllSiteResourcesByOrgQuerySchema = z.object({ - limit: z - .string() + pageSize: z.coerce + .number() // for prettier formatting + .int() + .positive() .optional() - .default("1000") - .transform(Number) - .pipe(z.int().positive()), - offset: z - .string() + .catch(20) + .default(20), + page: z.coerce + .number() // for prettier formatting + .int() + .min(0) .optional() - .default("0") - .transform(Number) - .pipe(z.int().nonnegative()) + .catch(1) + .default(1), + query: z.string().optional(), + mode: z.enum(["host", "cidr"]).optional().catch(undefined) }); export type ListAllSiteResourcesByOrgResponse = { @@ -35,8 +39,36 @@ export type ListAllSiteResourcesByOrgResponse = { siteNiceId: string; siteAddress: string | null; })[]; + pagination: { total: number; pageSize: number; page: number }; }; +function querySiteResourcesBase() { + return db + .select({ + siteResourceId: siteResources.siteResourceId, + siteId: siteResources.siteId, + orgId: siteResources.orgId, + niceId: siteResources.niceId, + name: siteResources.name, + mode: siteResources.mode, + protocol: siteResources.protocol, + proxyPort: siteResources.proxyPort, + destinationPort: siteResources.destinationPort, + destination: siteResources.destination, + enabled: siteResources.enabled, + alias: siteResources.alias, + aliasAddress: siteResources.aliasAddress, + tcpPortRangeString: siteResources.tcpPortRangeString, + udpPortRangeString: siteResources.udpPortRangeString, + disableIcmp: siteResources.disableIcmp, + siteName: sites.name, + siteNiceId: sites.niceId, + siteAddress: sites.address + }) + .from(siteResources) + .innerJoin(sites, eq(siteResources.siteId, sites.siteId)); +} + registry.registerPath({ method: "get", path: "/org/{orgId}/site-resources", @@ -80,39 +112,50 @@ export async function listAllSiteResourcesByOrg( } const { orgId } = parsedParams.data; - const { limit, offset } = parsedQuery.data; + const { page, pageSize, query, mode } = parsedQuery.data; + + let conditions = and(eq(siteResources.orgId, orgId)); + if (query) { + conditions = and( + conditions, + or( + ilike(siteResources.name, "%" + query + "%"), + ilike(siteResources.destination, "%" + query + "%"), + ilike(siteResources.alias, "%" + query + "%"), + ilike(siteResources.aliasAddress, "%" + query + "%"), + ilike(sites.name, "%" + query + "%") + ) + ); + } + + if (mode) { + conditions = and(conditions, eq(siteResources.mode, mode)); + } + + const baseQuery = querySiteResourcesBase().where(conditions); + + const countQuery = db.$count( + querySiteResourcesBase().where(conditions) + ); // Get all site resources for the org with site names - const siteResourcesList = await db - .select({ - siteResourceId: siteResources.siteResourceId, - siteId: siteResources.siteId, - orgId: siteResources.orgId, - niceId: siteResources.niceId, - name: siteResources.name, - mode: siteResources.mode, - protocol: siteResources.protocol, - proxyPort: siteResources.proxyPort, - destinationPort: siteResources.destinationPort, - destination: siteResources.destination, - enabled: siteResources.enabled, - alias: siteResources.alias, - aliasAddress: siteResources.aliasAddress, - tcpPortRangeString: siteResources.tcpPortRangeString, - udpPortRangeString: siteResources.udpPortRangeString, - disableIcmp: siteResources.disableIcmp, - siteName: sites.name, - siteNiceId: sites.niceId, - siteAddress: sites.address - }) - .from(siteResources) - .innerJoin(sites, eq(siteResources.siteId, sites.siteId)) - .where(eq(siteResources.orgId, orgId)) - .limit(limit) - .offset(offset); + const [siteResourcesList, totalCount] = await Promise.all([ + baseQuery + .limit(pageSize) + .offset(pageSize * (page - 1)) + .orderBy(asc(siteResources.siteResourceId)), + countQuery + ]); - return response(res, { - data: { siteResources: siteResourcesList }, + return response(res, { + data: { + siteResources: siteResourcesList, + pagination: { + total: totalCount, + pageSize, + page + } + }, success: true, error: false, message: "Site resources retrieved successfully", diff --git a/src/app/[orgId]/settings/resources/client/page.tsx b/src/app/[orgId]/settings/resources/client/page.tsx index ac85520e..f5e1a701 100644 --- a/src/app/[orgId]/settings/resources/client/page.tsx +++ b/src/app/[orgId]/settings/resources/client/page.tsx @@ -14,7 +14,7 @@ import { redirect } from "next/navigation"; export interface ClientResourcesPageProps { params: Promise<{ orgId: string }>; - searchParams: Promise<{ view?: string }>; + searchParams: Promise>; } export default async function ClientResourcesPage( @@ -22,22 +22,24 @@ export default async function ClientResourcesPage( ) { const params = await props.params; const t = await getTranslations(); - - let resources: ListResourcesResponse["resources"] = []; - try { - const res = await internal.get>( - `/org/${params.orgId}/resources`, - await authCookieHeader() - ); - resources = res.data.data.resources; - } catch (e) {} + const searchParams = new URLSearchParams(await props.searchParams); let siteResources: ListAllSiteResourcesByOrgResponse["siteResources"] = []; + let pagination: ListResourcesResponse["pagination"] = { + total: 0, + page: 1, + pageSize: 20 + }; try { const res = await internal.get< AxiosResponse - >(`/org/${params.orgId}/site-resources`, await authCookieHeader()); - siteResources = res.data.data.siteResources; + >( + `/org/${params.orgId}/site-resources?${searchParams.toString()}`, + await authCookieHeader() + ); + const responseData = res.data.data; + siteResources = responseData.siteResources; + pagination = responseData.pagination; } catch (e) {} let org = null; @@ -89,9 +91,10 @@ export default async function ClientResourcesPage( diff --git a/src/components/ClientResourcesTable.tsx b/src/components/ClientResourcesTable.tsx index c49cde8d..5dd9ae87 100644 --- a/src/components/ClientResourcesTable.tsx +++ b/src/components/ClientResourcesTable.tsx @@ -25,6 +25,11 @@ import CreateInternalResourceDialog from "@app/components/CreateInternalResource import EditInternalResourceDialog from "@app/components/EditInternalResourceDialog"; import { orgQueries } from "@app/lib/queries"; import { useQuery } from "@tanstack/react-query"; +import type { PaginationState } from "@tanstack/react-table"; +import { ControlledDataTable } from "./ui/controlled-data-table"; +import { useNavigationContext } from "@app/hooks/useNavigationContext"; +import { useDebouncedCallback } from "use-debounce"; +import { ColumnFilterButton } from "./ColumnFilterButton"; export type InternalResourceRow = { id: number; @@ -51,18 +56,22 @@ export type InternalResourceRow = { type ClientResourcesTableProps = { internalResources: InternalResourceRow[]; orgId: string; - defaultSort?: { - id: string; - desc: boolean; - }; + pagination: PaginationState; + rowCount: number; }; export default function ClientResourcesTable({ internalResources, orgId, - defaultSort + pagination, + rowCount }: ClientResourcesTableProps) { const router = useRouter(); + const { + navigate: filter, + isNavigating: isFiltering, + searchParams + } = useNavigationContext(); const t = useTranslations(); const { env } = useEnvContext(); @@ -180,9 +189,24 @@ export default function ClientResourcesTable({ accessorKey: "mode", friendlyName: t("editInternalResourceDialogMode"), header: () => ( - - {t("editInternalResourceDialogMode")} - + handleFilterChange("mode", value)} + searchPlaceholder={t("searchPlaceholder")} + emptyMessage={t("emptySearchOptions")} + label={t("editInternalResourceDialogMode")} + className="p-3" + /> ), cell: ({ row }) => { const resourceRow = row.original; @@ -300,6 +324,37 @@ export default function ClientResourcesTable({ } ]; + function handleFilterChange( + column: string, + value: string | undefined | null + ) { + searchParams.delete(column); + searchParams.delete("page"); + + if (value) { + searchParams.set(column, value); + } + filter({ + searchParams + }); + } + + const handlePaginationChange = (newPage: PaginationState) => { + searchParams.set("page", (newPage.pageIndex + 1).toString()); + searchParams.set("pageSize", newPage.pageSize.toString()); + filter({ + searchParams + }); + }; + + const handleSearchChange = useDebouncedCallback((query: string) => { + searchParams.set("query", query); + searchParams.delete("page"); + filter({ + searchParams + }); + }, 300); + return ( <> {selectedInternalResource && ( @@ -327,19 +382,20 @@ export default function ClientResourcesTable({ /> )} - setIsCreateDialogOpen(true)} addButtonText={t("resourceAdd")} + onSearch={handleSearchChange} onRefresh={refreshData} + onPaginationChange={handlePaginationChange} + pagination={pagination} + rowCount={rowCount} isRefreshing={isRefreshing} - defaultSort={defaultSort} - enableColumnVisibility={true} - persistColumnVisibility="internal-resources" + enableColumnVisibility columnVisibility={{ niceId: false, aliasAddress: false diff --git a/src/components/ProxyResourcesTable.tsx b/src/components/ProxyResourcesTable.tsx index ca8f0443..a22d96b6 100644 --- a/src/components/ProxyResourcesTable.tsx +++ b/src/components/ProxyResourcesTable.tsx @@ -2,9 +2,8 @@ import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog"; import CopyToClipboard from "@app/components/CopyToClipboard"; -import { DataTable } from "@app/components/ui/data-table"; -import { ExtendedColumnDef } from "@app/components/ui/data-table"; import { Button } from "@app/components/ui/button"; +import { ExtendedColumnDef } from "@app/components/ui/data-table"; import { DropdownMenu, DropdownMenuContent, @@ -14,13 +13,14 @@ import { import { InfoPopup } from "@app/components/ui/info-popup"; import { Switch } from "@app/components/ui/switch"; import { useEnvContext } from "@app/hooks/useEnvContext"; +import { useNavigationContext } from "@app/hooks/useNavigationContext"; import { toast } from "@app/hooks/useToast"; import { createApiClient, formatAxiosError } from "@app/lib/api"; import { UpdateResourceResponse } from "@server/routers/resource"; +import type { PaginationState } from "@tanstack/react-table"; import { AxiosResponse } from "axios"; import { ArrowRight, - ArrowUpDown, CheckCircle2, ChevronDown, Clock, @@ -31,14 +31,12 @@ import { } from "lucide-react"; import { useTranslations } from "next-intl"; import Link from "next/link"; -import { usePathname, useRouter, useSearchParams } from "next/navigation"; +import { useRouter } from "next/navigation"; import { useState, useTransition } from "react"; -import { ControlledDataTable } from "./ui/controlled-data-table"; -import type { PaginationState } from "@tanstack/react-table"; -import { useNavigationContext } from "@app/hooks/useNavigationContext"; import { useDebouncedCallback } from "use-debounce"; import z from "zod"; import { ColumnFilterButton } from "./ColumnFilterButton"; +import { ControlledDataTable } from "./ui/controlled-data-table"; export type TargetHealth = { targetId: number; @@ -584,10 +582,6 @@ export default function ProxyResourcesTable({ }); }, 300); - console.log({ - rowCount - }); - return ( <> {selectedResource && ( diff --git a/src/components/ui/controlled-data-table.tsx b/src/components/ui/controlled-data-table.tsx index c6fb505c..88f03384 100644 --- a/src/components/ui/controlled-data-table.tsx +++ b/src/components/ui/controlled-data-table.tsx @@ -130,7 +130,8 @@ export function ControlledDataTable({ }); console.log({ - pagination + pagination, + rowCount }); const table = useReactTable({ From 0547396213cc89814dec60ece0b2691e2f0ddde9 Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Fri, 6 Feb 2026 02:44:23 +0100 Subject: [PATCH 21/81] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20do=20not=20sort=20cl?= =?UTF-8?q?ient=20resources?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ClientResourcesTable.tsx | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/components/ClientResourcesTable.tsx b/src/components/ClientResourcesTable.tsx index 5dd9ae87..e3fcd11a 100644 --- a/src/components/ClientResourcesTable.tsx +++ b/src/components/ClientResourcesTable.tsx @@ -131,19 +131,7 @@ export default function ClientResourcesTable({ accessorKey: "name", enableHiding: false, friendlyName: t("name"), - header: ({ column }) => { - return ( - - ); - } + header: () => {t("name")} }, { id: "niceId", From ccddb9244d71c8cddcd0becae04a39c57a558b60 Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Fri, 6 Feb 2026 03:14:03 +0100 Subject: [PATCH 22/81] =?UTF-8?q?=F0=9F=8F=B7=EF=B8=8F=20add=20types=20on?= =?UTF-8?q?=20`mode`=20in=20sqlite?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/db/sqlite/schema/schema.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/db/sqlite/schema/schema.ts b/server/db/sqlite/schema/schema.ts index f26ecc08..85856369 100644 --- a/server/db/sqlite/schema/schema.ts +++ b/server/db/sqlite/schema/schema.ts @@ -247,7 +247,7 @@ export const siteResources = sqliteTable("siteResources", { .references(() => orgs.orgId, { onDelete: "cascade" }), niceId: text("niceId").notNull(), name: text("name").notNull(), - mode: text("mode").notNull(), // "host" | "cidr" | "port" + mode: text("mode").$type<"host" | "cidr">().notNull(), // "host" | "cidr" | "port" protocol: text("protocol"), // only for port mode proxyPort: integer("proxyPort"), // only for port mode destinationPort: integer("destinationPort"), // only for port mode From d521e79662cecd9c6cec6ef08f94c537bba2054a Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Fri, 6 Feb 2026 03:21:00 +0100 Subject: [PATCH 23/81] =?UTF-8?q?=F0=9F=8F=B7=EF=B8=8F=20fix=20types?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/routers/siteResource/createSiteResource.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/routers/siteResource/createSiteResource.ts b/server/routers/siteResource/createSiteResource.ts index b6140c27..48c298d3 100644 --- a/server/routers/siteResource/createSiteResource.ts +++ b/server/routers/siteResource/createSiteResource.ts @@ -284,7 +284,7 @@ export async function createSiteResource( niceId, orgId, name, - mode, + mode: mode as "host" | "cidr", // protocol: mode === "port" ? protocol : null, // proxyPort: mode === "port" ? proxyPort : null, // destinationPort: mode === "port" ? destinationPort : null, From 588f064c2585e291fff9c9555477a78a10d34a8d Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Fri, 6 Feb 2026 03:53:14 +0100 Subject: [PATCH 24/81] =?UTF-8?q?=F0=9F=9A=B8=20make=20resource=20enabled?= =?UTF-8?q?=20switch=20optimistic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ProxyResourcesTable.tsx | 63 ++++++++++++++++++++------ 1 file changed, 50 insertions(+), 13 deletions(-) diff --git a/src/components/ProxyResourcesTable.tsx b/src/components/ProxyResourcesTable.tsx index a22d96b6..ba69dec4 100644 --- a/src/components/ProxyResourcesTable.tsx +++ b/src/components/ProxyResourcesTable.tsx @@ -32,7 +32,13 @@ import { import { useTranslations } from "next-intl"; import Link from "next/link"; import { useRouter } from "next/navigation"; -import { useState, useTransition } from "react"; +import { + useOptimistic, + useRef, + useState, + useTransition, + type ComponentRef +} from "react"; import { useDebouncedCallback } from "use-debounce"; import z from "zod"; import { ColumnFilterButton } from "./ColumnFilterButton"; @@ -479,18 +485,9 @@ export default function ProxyResourcesTable({ /> ), cell: ({ row }) => ( - - startTransition(() => - toggleResourceEnabled(val, row.original.id) - ) - } + ) }, @@ -632,3 +629,43 @@ export default function ProxyResourcesTable({ ); } + +type ResourceEnabledFormProps = { + resource: ResourceRow; + onToggleResourceEnabled: ( + val: boolean, + resourceId: number + ) => Promise; +}; + +function ResourceEnabledForm({ + resource, + onToggleResourceEnabled +}: ResourceEnabledFormProps) { + const enabled = resource.http + ? !!resource.domainId && resource.enabled + : resource.enabled; + const [optimisticEnabled, setOptimisticEnabled] = useOptimistic(enabled); + + const formRef = useRef>(null); + + async function submitAction(formData: FormData) { + const newEnabled = !(formData.get("enabled") === "on"); + setOptimisticEnabled(newEnabled); + await onToggleResourceEnabled(newEnabled, resource.id); + } + + return ( +
+ formRef.current?.requestSubmit()} + /> + + ); +} From 4a31a7b84b539046cc82fb1ac7312f2b19401725 Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Fri, 6 Feb 2026 03:55:11 +0100 Subject: [PATCH 25/81] =?UTF-8?q?=F0=9F=9A=A8=20fix=20lint=20error?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../generateNewEnterpriseLicense.ts | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/server/private/routers/generatedLicense/generateNewEnterpriseLicense.ts b/server/private/routers/generatedLicense/generateNewEnterpriseLicense.ts index 7cffb9d7..94ee311b 100644 --- a/server/private/routers/generatedLicense/generateNewEnterpriseLicense.ts +++ b/server/private/routers/generatedLicense/generateNewEnterpriseLicense.ts @@ -37,8 +37,9 @@ export async function generateNewEnterpriseLicense( next: NextFunction ): Promise { try { - - const parsedParams = generateNewEnterpriseLicenseParamsSchema.safeParse(req.params); + const parsedParams = generateNewEnterpriseLicenseParamsSchema.safeParse( + req.params + ); if (!parsedParams.success) { return next( createHttpError( @@ -63,7 +64,10 @@ export async function generateNewEnterpriseLicense( const licenseData = req.body; - if (licenseData.tier != "big_license" && licenseData.tier != "small_license") { + if ( + licenseData.tier != "big_license" && + licenseData.tier != "small_license" + ) { return next( createHttpError( HttpCode.BAD_REQUEST, @@ -79,7 +83,8 @@ export async function generateNewEnterpriseLicense( return next( createHttpError( apiResponse.status || HttpCode.BAD_REQUEST, - apiResponse.message || "Failed to create license from Fossorial API" + apiResponse.message || + "Failed to create license from Fossorial API" ) ); } @@ -112,8 +117,11 @@ export async function generateNewEnterpriseLicense( ); } - const tier = licenseData.tier === "big_license" ? LicenseId.BIG_LICENSE : LicenseId.SMALL_LICENSE; - const tierPrice = getLicensePriceSet()[tier] + const tier = + licenseData.tier === "big_license" + ? LicenseId.BIG_LICENSE + : LicenseId.SMALL_LICENSE; + const tierPrice = getLicensePriceSet()[tier]; const session = await stripe!.checkout.sessions.create({ client_reference_id: keyId.toString(), @@ -122,7 +130,7 @@ export async function generateNewEnterpriseLicense( { price: tierPrice, // Use the standard tier quantity: 1 - }, + } ], // Start with the standard feature set that matches the free limits customer: customer.customerId, mode: "subscription", From 67b63d3084b5937cc2faf21afbe3f32c0de5d473 Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Fri, 6 Feb 2026 04:52:21 +0100 Subject: [PATCH 26/81] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20make=20code=20cleanr?= =?UTF-8?q?er?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/routers/client/listClients.ts | 87 +++++++++---------- server/routers/resource/listResources.ts | 29 +++---- server/routers/site/listSites.ts | 30 +++---- .../siteResource/listAllSiteResourcesByOrg.ts | 18 ++-- server/types/Pagination.ts | 5 ++ 5 files changed, 81 insertions(+), 88 deletions(-) create mode 100644 server/types/Pagination.ts diff --git a/server/routers/client/listClients.ts b/server/routers/client/listClients.ts index b4e2eb56..bb59755c 100644 --- a/server/routers/client/listClients.ts +++ b/server/routers/client/listClients.ts @@ -29,6 +29,7 @@ import { OpenAPITags, registry } from "@server/openApi"; import NodeCache from "node-cache"; import semver from "semver"; import { getUserDeviceName } from "@server/db/names"; +import type { PaginatedResponse } from "@server/types/Pagination"; const olmVersionCache = new NodeCache({ stdTTL: 3600 }); @@ -89,38 +90,29 @@ const listClientsParamsSchema = z.strictObject({ }); const listClientsSchema = z.object({ - limit: z - .string() + pageSize: z.coerce + .number() // for prettier formatting + .int() + .positive() .optional() - .default("1000") - .transform(Number) - .pipe(z.int().positive()), - offset: z - .string() + .catch(20) + .default(20), + page: z.coerce + .number() // for prettier formatting + .int() + .min(0) .optional() - .default("0") - .transform(Number) - .pipe(z.int().nonnegative()), + .catch(1) + .default(1), + query: z.string().optional(), + sort_by: z + .enum(["megabytesIn", "megabytesOut"]) + .optional() + .catch(undefined), filter: z.enum(["user", "machine"]).optional() }); -function queryClients( - orgId: string, - accessibleClientIds: number[], - filter?: "user" | "machine" -) { - const conditions = [ - inArray(clients.clientId, accessibleClientIds), - eq(clients.orgId, orgId) - ]; - - // Add filter condition based on filter type - if (filter === "user") { - conditions.push(isNotNull(clients.userId)); - } else if (filter === "machine") { - conditions.push(isNull(clients.userId)); - } - +function queryClientsBase() { return db .select({ clientId: clients.clientId, @@ -156,8 +148,7 @@ function queryClients( .leftJoin(orgs, eq(clients.orgId, orgs.orgId)) .leftJoin(olms, eq(clients.clientId, olms.clientId)) .leftJoin(users, eq(clients.userId, users.userId)) - .leftJoin(currentFingerprint, eq(olms.olmId, currentFingerprint.olmId)) - .where(and(...conditions)); + .leftJoin(currentFingerprint, eq(olms.olmId, currentFingerprint.olmId)); } async function getSiteAssociations(clientIds: number[]) { @@ -175,7 +166,7 @@ async function getSiteAssociations(clientIds: number[]) { .where(inArray(clientSitesAssociationsCache.clientId, clientIds)); } -type ClientWithSites = Awaited>[0] & { +type ClientWithSites = Awaited>[0] & { sites: Array<{ siteId: number; siteName: string | null; @@ -186,10 +177,9 @@ type ClientWithSites = Awaited>[0] & { type OlmWithUpdateAvailable = ClientWithSites; -export type ListClientsResponse = { +export type ListClientsResponse = PaginatedResponse<{ clients: Array; - pagination: { total: number; limit: number; offset: number }; -}; +}>; registry.registerPath({ method: "get", @@ -218,7 +208,7 @@ export async function listClients( ) ); } - const { limit, offset, filter } = parsedQuery.data; + const { page, pageSize, query, filter } = parsedQuery.data; const parsedParams = listClientsParamsSchema.safeParse(req.params); if (!parsedParams.success) { @@ -267,28 +257,31 @@ export async function listClients( const accessibleClientIds = accessibleClients.map( (client) => client.clientId ); - const baseQuery = queryClients(orgId, accessibleClientIds, filter); + const baseQuery = queryClientsBase(); // Get client count with filter - const countConditions = [ + const conditions = [ inArray(clients.clientId, accessibleClientIds), eq(clients.orgId, orgId) ]; if (filter === "user") { - countConditions.push(isNotNull(clients.userId)); + conditions.push(isNotNull(clients.userId)); } else if (filter === "machine") { - countConditions.push(isNull(clients.userId)); + conditions.push(isNull(clients.userId)); } - const countQuery = db - .select({ count: count() }) - .from(clients) - .where(and(...countConditions)); + const countQuery = db.$count( + queryClientsBase().where(and(...conditions)) + ); - const clientsList = await baseQuery.limit(limit).offset(offset); - const totalCountResult = await countQuery; - const totalCount = totalCountResult[0].count; + const [clientsList, totalCount] = await Promise.all([ + baseQuery + .where(and(...conditions)) + .limit(page) + .offset(pageSize * (page - 1)), + countQuery + ]); // Get associated sites for all clients const clientIds = clientsList.map((client) => client.clientId); @@ -368,8 +361,8 @@ export async function listClients( clients: olmsWithUpdates, pagination: { total: totalCount, - limit, - offset + page, + pageSize } }, success: true, diff --git a/server/routers/resource/listResources.ts b/server/routers/resource/listResources.ts index 26a0d613..cf0769ca 100644 --- a/server/routers/resource/listResources.ts +++ b/server/routers/resource/listResources.ts @@ -33,6 +33,7 @@ import { import logger from "@server/logger"; import { fromZodError } from "zod-validation-error"; import { OpenAPITags, registry } from "@server/openApi"; +import type { PaginatedResponse } from "@server/types/Pagination"; const listResourcesParamsSchema = z.strictObject({ orgId: z.string() @@ -171,10 +172,9 @@ function queryResourcesBase() { ); } -export type ListResourcesResponse = { +export type ListResourcesResponse = PaginatedResponse<{ resources: ResourceWithTargets[]; - pagination: { total: number; pageSize: number; page: number }; -}; +}>; registry.registerPath({ method: "get", @@ -268,16 +268,15 @@ export async function listResources( (resource) => resource.resourceId ); - let conditions = and( + const conditions = [ and( inArray(resources.resourceId, accessibleResourceIds), eq(resources.orgId, orgId) ) - ); + ]; if (query) { - conditions = and( - conditions, + conditions.push( or( ilike(resources.name, "%" + query + "%"), ilike(resources.fullDomain, "%" + query + "%") @@ -285,17 +284,16 @@ export async function listResources( ); } if (typeof enabled !== "undefined") { - conditions = and(conditions, eq(resources.enabled, enabled)); + conditions.push(eq(resources.enabled, enabled)); } if (typeof authState !== "undefined") { switch (authState) { case "none": - conditions = and(conditions, eq(resources.http, false)); + conditions.push(eq(resources.http, false)); break; case "protected": - conditions = and( - conditions, + conditions.push( or( eq(resources.sso, true), eq(resources.emailWhitelistEnabled, true), @@ -306,8 +304,7 @@ export async function listResources( ); break; case "not_protected": - conditions = and( - conditions, + conditions.push( not(eq(resources.sso, true)), not(eq(resources.emailWhitelistEnabled, true)), isNull(resourceHeaderAuth.headerAuthId), @@ -318,7 +315,7 @@ export async function listResources( } } - let aggregateFilters: SQL | null | undefined = null; + let aggregateFilters: SQL | undefined = sql`1 = 1`; if (typeof healthStatus !== "undefined") { switch (healthStatus) { @@ -354,8 +351,8 @@ export async function listResources( } const baseQuery = queryResourcesBase() - .where(conditions) - .having(aggregateFilters ?? sql`1 = 1`); + .where(and(...conditions)) + .having(aggregateFilters); // we need to add `as` so that drizzle filters the result as a subquery const countQuery = db.$count(baseQuery.as("filtered_resources")); diff --git a/server/routers/site/listSites.ts b/server/routers/site/listSites.ts index c65f8d10..cc292499 100644 --- a/server/routers/site/listSites.ts +++ b/server/routers/site/listSites.ts @@ -22,6 +22,7 @@ import { fromError } from "zod-validation-error"; import { OpenAPITags, registry } from "@server/openApi"; import semver from "semver"; import cache from "@server/lib/cache"; +import type { PaginatedResponse } from "@server/types/Pagination"; async function getLatestNewtVersion(): Promise { try { @@ -111,9 +112,6 @@ const listSitesSchema = z.object({ .catch(undefined) }); -function countSitesBase() { - return db.select({ count: count() }).from(sites); -} function querySitesBase() { return db .select({ @@ -148,10 +146,9 @@ type SiteWithUpdateAvailable = Awaited>[0] & { newtUpdateAvailable?: boolean; }; -export type ListSitesResponse = { +export type ListSitesResponse = PaginatedResponse<{ sites: SiteWithUpdateAvailable[]; - pagination: { total: number; pageSize: number; page: number }; -}; +}>; registry.registerPath({ method: "get", @@ -227,13 +224,14 @@ export async function listSites( const accessibleSiteIds = accessibleSites.map((site) => site.siteId); - let conditions = and( - inArray(sites.siteId, accessibleSiteIds), - eq(sites.orgId, orgId) - ); + const conditions = [ + and( + inArray(sites.siteId, accessibleSiteIds), + eq(sites.orgId, orgId) + ) + ]; if (query) { - conditions = and( - conditions, + conditions.push( or( ilike(sites.name, "%" + query + "%"), ilike(sites.niceId, "%" + query + "%") @@ -241,13 +239,15 @@ export async function listSites( ); } if (typeof online !== "undefined") { - conditions = and(conditions, eq(sites.online, online)); + conditions.push(eq(sites.online, online)); } - const baseQuery = querySitesBase().where(conditions); + const baseQuery = querySitesBase().where(and(...conditions)); // we need to add `as` so that drizzle filters the result as a subquery - const countQuery = db.$count(querySitesBase().where(conditions)); + const countQuery = db.$count( + querySitesBase().where(and(...conditions)) + ); const siteListQuery = baseQuery .limit(pageSize) diff --git a/server/routers/siteResource/listAllSiteResourcesByOrg.ts b/server/routers/siteResource/listAllSiteResourcesByOrg.ts index 2392eb76..f15d2ecc 100644 --- a/server/routers/siteResource/listAllSiteResourcesByOrg.ts +++ b/server/routers/siteResource/listAllSiteResourcesByOrg.ts @@ -9,6 +9,7 @@ import { eq, and, asc, ilike, or } from "drizzle-orm"; import { fromError } from "zod-validation-error"; import logger from "@server/logger"; import { OpenAPITags, registry } from "@server/openApi"; +import type { PaginatedResponse } from "@server/types/Pagination"; const listAllSiteResourcesByOrgParamsSchema = z.strictObject({ orgId: z.string() @@ -33,14 +34,13 @@ const listAllSiteResourcesByOrgQuerySchema = z.object({ mode: z.enum(["host", "cidr"]).optional().catch(undefined) }); -export type ListAllSiteResourcesByOrgResponse = { +export type ListAllSiteResourcesByOrgResponse = PaginatedResponse<{ siteResources: (SiteResource & { siteName: string; siteNiceId: string; siteAddress: string | null; })[]; - pagination: { total: number; pageSize: number; page: number }; -}; +}>; function querySiteResourcesBase() { return db @@ -114,10 +114,9 @@ export async function listAllSiteResourcesByOrg( const { orgId } = parsedParams.data; const { page, pageSize, query, mode } = parsedQuery.data; - let conditions = and(eq(siteResources.orgId, orgId)); + const conditions = [and(eq(siteResources.orgId, orgId))]; if (query) { - conditions = and( - conditions, + conditions.push( or( ilike(siteResources.name, "%" + query + "%"), ilike(siteResources.destination, "%" + query + "%"), @@ -129,16 +128,15 @@ export async function listAllSiteResourcesByOrg( } if (mode) { - conditions = and(conditions, eq(siteResources.mode, mode)); + conditions.push(eq(siteResources.mode, mode)); } - const baseQuery = querySiteResourcesBase().where(conditions); + const baseQuery = querySiteResourcesBase().where(and(...conditions)); const countQuery = db.$count( - querySiteResourcesBase().where(conditions) + querySiteResourcesBase().where(and(...conditions)) ); - // Get all site resources for the org with site names const [siteResourcesList, totalCount] = await Promise.all([ baseQuery .limit(pageSize) diff --git a/server/types/Pagination.ts b/server/types/Pagination.ts new file mode 100644 index 00000000..b0f5edfe --- /dev/null +++ b/server/types/Pagination.ts @@ -0,0 +1,5 @@ +export type Pagination = { total: number; pageSize: number; page: number }; + +export type PaginatedResponse = T & { + pagination: Pagination; +}; From 9f2fd34e995b8dbf32fe06aa7c60a9df9d014a8a Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Fri, 6 Feb 2026 05:37:44 +0100 Subject: [PATCH 27/81] =?UTF-8?q?=F0=9F=9A=A7=20wip:=20user=20devices=20en?= =?UTF-8?q?dpoint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/routers/client/index.ts | 1 + server/routers/client/listClients.ts | 30 +- server/routers/client/listUserDevices.ts | 302 ++++++++++++++++++ server/routers/external.ts | 7 + server/routers/integration.ts | 7 + server/routers/resource/listResources.ts | 2 +- .../[orgId]/settings/clients/user/page.tsx | 81 +++-- .../CreateInternalResourceDialog.tsx | 5 +- src/components/EditInternalResourceDialog.tsx | 5 +- src/lib/queries.ts | 8 +- 10 files changed, 374 insertions(+), 74 deletions(-) create mode 100644 server/routers/client/listUserDevices.ts diff --git a/server/routers/client/index.ts b/server/routers/client/index.ts index 34614cc8..e195d1c5 100644 --- a/server/routers/client/index.ts +++ b/server/routers/client/index.ts @@ -6,6 +6,7 @@ export * from "./unarchiveClient"; export * from "./blockClient"; export * from "./unblockClient"; export * from "./listClients"; +export * from "./listUserDevices"; export * from "./updateClient"; export * from "./getClient"; export * from "./createUserClient"; diff --git a/server/routers/client/listClients.ts b/server/routers/client/listClients.ts index bb59755c..5c3702dd 100644 --- a/server/routers/client/listClients.ts +++ b/server/routers/client/listClients.ts @@ -105,11 +105,7 @@ const listClientsSchema = z.object({ .catch(1) .default(1), query: z.string().optional(), - sort_by: z - .enum(["megabytesIn", "megabytesOut"]) - .optional() - .catch(undefined), - filter: z.enum(["user", "machine"]).optional() + sort_by: z.enum(["megabytesIn", "megabytesOut"]).optional().catch(undefined) }); function queryClientsBase() { @@ -134,15 +130,7 @@ function queryClientsBase() { approvalState: clients.approvalState, olmArchived: olms.archived, archived: clients.archived, - blocked: clients.blocked, - deviceModel: currentFingerprint.deviceModel, - fingerprintPlatform: currentFingerprint.platform, - fingerprintOsVersion: currentFingerprint.osVersion, - fingerprintKernelVersion: currentFingerprint.kernelVersion, - fingerprintArch: currentFingerprint.arch, - fingerprintSerialNumber: currentFingerprint.serialNumber, - fingerprintUsername: currentFingerprint.username, - fingerprintHostname: currentFingerprint.hostname + blocked: clients.blocked }) .from(clients) .leftJoin(orgs, eq(clients.orgId, orgs.orgId)) @@ -208,7 +196,7 @@ export async function listClients( ) ); } - const { page, pageSize, query, filter } = parsedQuery.data; + const { page, pageSize, query } = parsedQuery.data; const parsedParams = listClientsParamsSchema.safeParse(req.params); if (!parsedParams.success) { @@ -262,15 +250,10 @@ export async function listClients( // Get client count with filter const conditions = [ inArray(clients.clientId, accessibleClientIds), - eq(clients.orgId, orgId) + eq(clients.orgId, orgId), + isNull(clients.userId) ]; - if (filter === "user") { - conditions.push(isNotNull(clients.userId)); - } else if (filter === "machine") { - conditions.push(isNull(clients.userId)); - } - const countQuery = db.$count( queryClientsBase().where(and(...conditions)) ); @@ -312,11 +295,8 @@ export async function listClients( // Merge clients with their site associations and replace name with device name const clientsWithSites = clientsList.map((client) => { - const model = client.deviceModel || null; - const newName = getUserDeviceName(model, client.name); return { ...client, - name: newName, sites: sitesByClient[client.clientId] || [] }; }); diff --git a/server/routers/client/listUserDevices.ts b/server/routers/client/listUserDevices.ts new file mode 100644 index 00000000..95a5b6ca --- /dev/null +++ b/server/routers/client/listUserDevices.ts @@ -0,0 +1,302 @@ +import { + clients, + currentFingerprint, + db, + olms, + orgs, + roleClients, + userClients, + users +} from "@server/db"; +import { getUserDeviceName } from "@server/db/names"; +import response from "@server/lib/response"; +import logger from "@server/logger"; +import { OpenAPITags, registry } from "@server/openApi"; +import HttpCode from "@server/types/HttpCode"; +import type { PaginatedResponse } from "@server/types/Pagination"; +import { and, eq, inArray, isNotNull, or, sql } from "drizzle-orm"; +import { NextFunction, Request, Response } from "express"; +import createHttpError from "http-errors"; +import NodeCache from "node-cache"; +import semver from "semver"; +import { z } from "zod"; +import { fromError } from "zod-validation-error"; + +const olmVersionCache = new NodeCache({ stdTTL: 3600 }); + +async function getLatestOlmVersion(): Promise { + try { + const cachedVersion = olmVersionCache.get("latestOlmVersion"); + if (cachedVersion) { + return cachedVersion; + } + + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 1500); + + const response = await fetch( + "https://api.github.com/repos/fosrl/olm/tags", + { + signal: controller.signal + } + ); + + clearTimeout(timeoutId); + + if (!response.ok) { + logger.warn( + `Failed to fetch latest Olm version from GitHub: ${response.status} ${response.statusText}` + ); + return null; + } + + let tags = await response.json(); + if (!Array.isArray(tags) || tags.length === 0) { + logger.warn("No tags found for Olm repository"); + return null; + } + tags = tags.filter((version) => !version.name.includes("rc")); + const latestVersion = tags[0].name; + + olmVersionCache.set("latestOlmVersion", latestVersion); + + return latestVersion; + } catch (error: any) { + if (error.name === "AbortError") { + logger.warn("Request to fetch latest Olm version timed out (1.5s)"); + } else if (error.cause?.code === "UND_ERR_CONNECT_TIMEOUT") { + logger.warn("Connection timeout while fetching latest Olm version"); + } else { + logger.warn( + "Error fetching latest Olm version:", + error.message || error + ); + } + return null; + } +} + +const listUserDevicesParamsSchema = z.strictObject({ + orgId: z.string() +}); + +const listUserDevicesSchema = z.object({ + pageSize: z.coerce + .number() // for prettier formatting + .int() + .positive() + .optional() + .catch(20) + .default(20), + page: z.coerce + .number() // for prettier formatting + .int() + .min(0) + .optional() + .catch(1) + .default(1), + query: z.string().optional(), + sort_by: z.enum(["megabytesIn", "megabytesOut"]).optional().catch(undefined) +}); + +function queryUserDevicesBase() { + return db + .select({ + clientId: clients.clientId, + orgId: clients.orgId, + name: clients.name, + pubKey: clients.pubKey, + subnet: clients.subnet, + megabytesIn: clients.megabytesIn, + megabytesOut: clients.megabytesOut, + orgName: orgs.name, + type: clients.type, + online: clients.online, + olmVersion: olms.version, + userId: clients.userId, + username: users.username, + userEmail: users.email, + niceId: clients.niceId, + agent: olms.agent, + approvalState: clients.approvalState, + olmArchived: olms.archived, + archived: clients.archived, + blocked: clients.blocked, + deviceModel: currentFingerprint.deviceModel, + fingerprintPlatform: currentFingerprint.platform, + fingerprintOsVersion: currentFingerprint.osVersion, + fingerprintKernelVersion: currentFingerprint.kernelVersion, + fingerprintArch: currentFingerprint.arch, + fingerprintSerialNumber: currentFingerprint.serialNumber, + fingerprintUsername: currentFingerprint.username, + fingerprintHostname: currentFingerprint.hostname + }) + .from(clients) + .leftJoin(orgs, eq(clients.orgId, orgs.orgId)) + .leftJoin(olms, eq(clients.clientId, olms.clientId)) + .leftJoin(users, eq(clients.userId, users.userId)) + .leftJoin(currentFingerprint, eq(olms.olmId, currentFingerprint.olmId)); +} + +type OlmWithUpdateAvailable = Awaited< + ReturnType +>[0] & { + olmUpdateAvailable?: boolean; +}; + +export type ListUserDevicesResponse = PaginatedResponse<{ + devices: Array; +}>; + +registry.registerPath({ + method: "get", + path: "/org/{orgId}/user-devices", + description: "List all user devices for an organization.", + tags: [OpenAPITags.Client, OpenAPITags.Org], + request: { + query: listUserDevicesSchema, + params: listUserDevicesParamsSchema + }, + responses: {} +}); + +export async function listUserDevices( + req: Request, + res: Response, + next: NextFunction +): Promise { + try { + const parsedQuery = listUserDevicesSchema.safeParse(req.query); + if (!parsedQuery.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + fromError(parsedQuery.error) + ) + ); + } + const { page, pageSize, query } = parsedQuery.data; + + const parsedParams = listUserDevicesParamsSchema.safeParse(req.params); + if (!parsedParams.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + fromError(parsedParams.error) + ) + ); + } + const { orgId } = parsedParams.data; + + if (req.user && orgId && orgId !== req.userOrgId) { + return next( + createHttpError( + HttpCode.FORBIDDEN, + "User does not have access to this organization" + ) + ); + } + + let accessibleClients; + if (req.user) { + accessibleClients = await db + .select({ + clientId: sql`COALESCE(${userClients.clientId}, ${roleClients.clientId})` + }) + .from(userClients) + .fullJoin( + roleClients, + eq(userClients.clientId, roleClients.clientId) + ) + .where( + or( + eq(userClients.userId, req.user!.userId), + eq(roleClients.roleId, req.userOrgRoleId!) + ) + ); + } else { + accessibleClients = await db + .select({ clientId: clients.clientId }) + .from(clients) + .where(eq(clients.orgId, orgId)); + } + + const accessibleClientIds = accessibleClients.map( + (client) => client.clientId + ); + // Get client count with filter + const conditions = [ + inArray(clients.clientId, accessibleClientIds), + eq(clients.orgId, orgId), + isNotNull(clients.userId) + ]; + + const baseQuery = queryUserDevicesBase().where(and(...conditions)); + + const countQuery = db.$count(baseQuery.as("filtered_clients")); + + const [clientsList, totalCount] = await Promise.all([ + baseQuery.limit(pageSize).offset(pageSize * (page - 1)), + countQuery + ]); + + // Merge clients with their site associations and replace name with device name + const olmsWithUpdates: OlmWithUpdateAvailable[] = clientsList.map( + (client) => { + const model = client.deviceModel || null; + const newName = getUserDeviceName(model, client.name); + const OlmWithUpdate: OlmWithUpdateAvailable = { + ...client, + name: newName + }; + // Initially set to false, will be updated if version check succeeds + OlmWithUpdate.olmUpdateAvailable = false; + return OlmWithUpdate; + } + ); + + // Try to get the latest version, but don't block if it fails + try { + const latestOlmVersion = await getLatestOlmVersion(); + + if (latestOlmVersion) { + olmsWithUpdates.forEach((client) => { + try { + client.olmUpdateAvailable = semver.lt( + client.olmVersion ? client.olmVersion : "", + latestOlmVersion + ); + } catch (error) { + client.olmUpdateAvailable = false; + } + }); + } + } catch (error) { + // Log the error but don't let it block the response + logger.warn( + "Failed to check for OLM updates, continuing without update info:", + error + ); + } + + return response(res, { + data: { + devices: olmsWithUpdates, + pagination: { + total: totalCount, + page, + pageSize + } + }, + success: true, + error: false, + message: "Clients retrieved successfully", + status: HttpCode.OK + }); + } catch (error) { + logger.error(error); + return next( + createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred") + ); + } +} diff --git a/server/routers/external.ts b/server/routers/external.ts index aff01bfa..bfffeaca 100644 --- a/server/routers/external.ts +++ b/server/routers/external.ts @@ -143,6 +143,13 @@ authenticated.get( client.listClients ); +authenticated.get( + "/org/:orgId/user-devices", + verifyOrgAccess, + verifyUserHasAction(ActionsEnum.listClients), + client.listUserDevices +); + authenticated.get( "/client/:clientId", verifyClientAccess, diff --git a/server/routers/integration.ts b/server/routers/integration.ts index 9bb26398..85c9009d 100644 --- a/server/routers/integration.ts +++ b/server/routers/integration.ts @@ -818,6 +818,13 @@ authenticated.get( client.listClients ); +authenticated.get( + "/org/:orgId/user-devices", + verifyApiKeyOrgAccess, + verifyApiKeyHasAction(ActionsEnum.listClients), + client.listUserDevices +); + authenticated.get( "/client/:clientId", verifyApiKeyClientAccess, diff --git a/server/routers/resource/listResources.ts b/server/routers/resource/listResources.ts index cf0769ca..090ea971 100644 --- a/server/routers/resource/listResources.ts +++ b/server/routers/resource/listResources.ts @@ -380,7 +380,7 @@ export async function listResources( hcEnabled: targetHealthCheck.hcEnabled }) .from(targets) - .where(sql`${targets.resourceId} in ${resourceIdList}`) + .where(inArray(targets.resourceId, resourceIdList)) .leftJoin( targetHealthCheck, eq(targetHealthCheck.targetId, targets.targetId) diff --git a/src/app/[orgId]/settings/clients/user/page.tsx b/src/app/[orgId]/settings/clients/user/page.tsx index 35a2b2e3..d047a60f 100644 --- a/src/app/[orgId]/settings/clients/user/page.tsx +++ b/src/app/[orgId]/settings/clients/user/page.tsx @@ -1,11 +1,12 @@ -import { internal } from "@app/lib/api"; -import { authCookieHeader } from "@app/lib/api/cookies"; -import { AxiosResponse } from "axios"; import SettingsSectionTitle from "@app/components/SettingsSectionTitle"; -import { ListClientsResponse } from "@server/routers/client"; -import { getTranslations } from "next-intl/server"; import type { ClientRow } from "@app/components/UserDevicesTable"; import UserDevicesTable from "@app/components/UserDevicesTable"; +import { internal } from "@app/lib/api"; +import { authCookieHeader } from "@app/lib/api/cookies"; +import { type ListUserDevicesResponse } from "@server/routers/client"; +import type { Pagination } from "@server/types/Pagination"; +import { AxiosResponse } from "axios"; +import { getTranslations } from "next-intl/server"; type ClientsPageProps = { params: Promise<{ orgId: string }>; @@ -18,14 +19,21 @@ export default async function ClientsPage(props: ClientsPageProps) { const params = await props.params; - let userClients: ListClientsResponse["clients"] = []; + let userClients: ListUserDevicesResponse["devices"] = []; + + let pagination: Pagination = { + page: 1, + total: 0, + pageSize: 20 + }; try { - const userRes = await internal.get>( - `/org/${params.orgId}/clients?filter=user`, - await authCookieHeader() - ); - userClients = userRes.data.data.clients; + const userRes = await internal.get< + AxiosResponse + >(`/org/${params.orgId}/user-devices`, await authCookieHeader()); + const responseData = userRes.data.data; + userClients = responseData.devices; + pagination = responseData.pagination; } catch (e) {} function formatSize(mb: number): string { @@ -39,31 +47,29 @@ export default async function ClientsPage(props: ClientsPageProps) { } const mapClientToRow = ( - client: ListClientsResponse["clients"][0] + client: ListUserDevicesResponse["devices"][number] ): ClientRow => { // Build fingerprint object if any fingerprint data exists const hasFingerprintData = - (client as any).fingerprintPlatform || - (client as any).fingerprintOsVersion || - (client as any).fingerprintKernelVersion || - (client as any).fingerprintArch || - (client as any).fingerprintSerialNumber || - (client as any).fingerprintUsername || - (client as any).fingerprintHostname || - (client as any).deviceModel; + client.fingerprintPlatform || + client.fingerprintOsVersion || + client.fingerprintKernelVersion || + client.fingerprintArch || + client.fingerprintSerialNumber || + client.fingerprintUsername || + client.fingerprintHostname || + client.deviceModel; const fingerprint = hasFingerprintData ? { - platform: (client as any).fingerprintPlatform || null, - osVersion: (client as any).fingerprintOsVersion || null, - kernelVersion: - (client as any).fingerprintKernelVersion || null, - arch: (client as any).fingerprintArch || null, - deviceModel: (client as any).deviceModel || null, - serialNumber: - (client as any).fingerprintSerialNumber || null, - username: (client as any).fingerprintUsername || null, - hostname: (client as any).fingerprintHostname || null + platform: client.fingerprintPlatform, + osVersion: client.fingerprintOsVersion, + kernelVersion: client.fingerprintKernelVersion, + arch: client.fingerprintArch, + deviceModel: client.deviceModel, + serialNumber: client.fingerprintSerialNumber, + username: client.fingerprintUsername, + hostname: client.fingerprintHostname } : null; @@ -71,19 +77,19 @@ export default async function ClientsPage(props: ClientsPageProps) { name: client.name, id: client.clientId, subnet: client.subnet.split("/")[0], - mbIn: formatSize(client.megabytesIn || 0), - mbOut: formatSize(client.megabytesOut || 0), + mbIn: formatSize(client.megabytesIn ?? 0), + mbOut: formatSize(client.megabytesOut ?? 0), orgId: params.orgId, online: client.online, olmVersion: client.olmVersion || undefined, - olmUpdateAvailable: client.olmUpdateAvailable || false, + olmUpdateAvailable: Boolean(client.olmUpdateAvailable), userId: client.userId, username: client.username, userEmail: client.userEmail, niceId: client.niceId, agent: client.agent, - archived: client.archived || false, - blocked: client.blocked || false, + archived: Boolean(client.archived), + blocked: Boolean(client.blocked), approvalState: client.approvalState, fingerprint }; @@ -91,6 +97,11 @@ export default async function ClientsPage(props: ClientsPageProps) { const userClientRows: ClientRow[] = userClients.map(mapClientToRow); + console.log({ + userClientRows, + pagination + }); + return ( <> ; + filters?: z.infer; }) => queryOptions({ queryKey: ["ORG", orgId, "CLIENTS", filters] as const, queryFn: async ({ signal, meta }) => { const sp = new URLSearchParams({ - ...filters, - limit: (filters.limit ?? 1000).toString() + pageSize: (filters?.pageSize ?? 1000).toString() }); const res = await meta!.api.get< From 49435398a8af8c6b1fee9010fe567bb342a172e1 Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Sat, 7 Feb 2026 02:50:59 +0100 Subject: [PATCH 28/81] =?UTF-8?q?=F0=9F=94=A5=20cleanup=20imports?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/routers/client/listClients.ts | 32 +++++++++++----------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/server/routers/client/listClients.ts b/server/routers/client/listClients.ts index 5c3702dd..588f1edd 100644 --- a/server/routers/client/listClients.ts +++ b/server/routers/client/listClients.ts @@ -1,35 +1,27 @@ -import { db, olms, users } from "@server/db"; import { clients, + clientSitesAssociationsCache, + currentFingerprint, + db, + olms, orgs, roleClients, sites, userClients, - clientSitesAssociationsCache, - currentFingerprint + users } from "@server/db"; -import logger from "@server/logger"; -import HttpCode from "@server/types/HttpCode"; import response from "@server/lib/response"; -import { - and, - count, - eq, - inArray, - isNotNull, - isNull, - or, - sql -} from "drizzle-orm"; +import logger from "@server/logger"; +import { OpenAPITags, registry } from "@server/openApi"; +import HttpCode from "@server/types/HttpCode"; +import type { PaginatedResponse } from "@server/types/Pagination"; +import { and, eq, inArray, isNull, or, sql } from "drizzle-orm"; import { NextFunction, Request, Response } from "express"; import createHttpError from "http-errors"; -import { z } from "zod"; -import { fromError } from "zod-validation-error"; -import { OpenAPITags, registry } from "@server/openApi"; import NodeCache from "node-cache"; import semver from "semver"; -import { getUserDeviceName } from "@server/db/names"; -import type { PaginatedResponse } from "@server/types/Pagination"; +import { z } from "zod"; +import { fromError } from "zod-validation-error"; const olmVersionCache = new NodeCache({ stdTTL: 3600 }); From fd7f6b2b998a5ed5a92256688774e254eb21a085 Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Sat, 7 Feb 2026 02:51:28 +0100 Subject: [PATCH 29/81] =?UTF-8?q?=E2=9C=A8=20filter=20user=20devices=20API?= =?UTF-8?q?=20finished?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/routers/client/listUserDevices.ts | 158 ++++++++++++++++++++++- 1 file changed, 151 insertions(+), 7 deletions(-) diff --git a/server/routers/client/listUserDevices.ts b/server/routers/client/listUserDevices.ts index 95a5b6ca..7ef5f784 100644 --- a/server/routers/client/listUserDevices.ts +++ b/server/routers/client/listUserDevices.ts @@ -5,6 +5,7 @@ import { olms, orgs, roleClients, + sites, userClients, users } from "@server/db"; @@ -14,7 +15,20 @@ import logger from "@server/logger"; import { OpenAPITags, registry } from "@server/openApi"; import HttpCode from "@server/types/HttpCode"; import type { PaginatedResponse } from "@server/types/Pagination"; -import { and, eq, inArray, isNotNull, or, sql } from "drizzle-orm"; +import { + and, + asc, + desc, + eq, + ilike, + inArray, + isNotNull, + isNull, + not, + or, + sql, + type SQL +} from "drizzle-orm"; import { NextFunction, Request, Response } from "express"; import createHttpError from "http-errors"; import NodeCache from "node-cache"; @@ -96,7 +110,40 @@ const listUserDevicesSchema = z.object({ .catch(1) .default(1), query: z.string().optional(), - sort_by: z.enum(["megabytesIn", "megabytesOut"]).optional().catch(undefined) + sort_by: z + .enum(["megabytesIn", "megabytesOut"]) + .optional() + .catch(undefined), + order: z.enum(["asc", "desc"]).optional().default("asc").catch("asc"), + online: z + .enum(["true", "false"]) + .transform((v) => v === "true") + .optional() + .catch(undefined), + agent: z + .enum([ + "windows", + "android", + "cli", + "macos", + "ios", + "ipados", + "unknown" + ]) + .optional() + .catch(undefined), + filters: z.preprocess( + (val: string) => { + return val.split(","); // the search query array is an array joined by a comma + }, + z + .array( + z.enum(["active", "pending", "denied", "blocked", "archived"]) + ) + .optional() + .default(["active", "pending"]) + .catch(["active", "pending"]) + ) }); function queryUserDevicesBase() { @@ -175,7 +222,28 @@ export async function listUserDevices( ) ); } - const { page, pageSize, query } = parsedQuery.data; + const { + page, + pageSize, + query, + sort_by, + online, + filters, + agent, + order + } = parsedQuery.data; + + console.log({ query: req.query }); + console.log({ + page, + pageSize, + query, + sort_by, + online, + filters, + agent, + order + }); const parsedParams = listUserDevicesParamsSchema.safeParse(req.params); if (!parsedParams.success) { @@ -226,17 +294,93 @@ export async function listUserDevices( ); // Get client count with filter const conditions = [ - inArray(clients.clientId, accessibleClientIds), - eq(clients.orgId, orgId), - isNotNull(clients.userId) + and( + inArray(clients.clientId, accessibleClientIds), + eq(clients.orgId, orgId), + isNotNull(clients.userId) + ) ]; + if (query) { + conditions.push( + or( + ilike(clients.name, "%" + query + "%"), + ilike(users.name, "%" + query + "%") + ) + ); + } + + if (typeof online !== "undefined") { + conditions.push(eq(clients.online, online)); + } + + const agentValueMap = { + windows: "Pangolin Windows", + android: "Pangolin Android", + ios: "Pangolin iOS", + ipados: "Pangolin iPadOS", + macos: "Pangolin macOS", + cli: "Pangolin CLI" + } satisfies Record< + Exclude, + string + >; + if (typeof agent !== "undefined") { + if (agent === "unknown") { + conditions.push(isNull(olms.agent)); + } else { + conditions.push(eq(olms.agent, agentValueMap[agent])); + } + } + + if (filters.length > 0) { + const filterAggregates: (SQL | undefined)[] = []; + + if (filters.includes("active")) { + filterAggregates.push( + and( + eq(clients.archived, false), + eq(clients.blocked, false), + or( + eq(clients.approvalState, "approved"), + isNull(clients.approvalState) // approval state of `NULL` means approved by default + ) + ) + ); + } + if (filters.includes("pending")) { + filterAggregates.push(eq(clients.approvalState, "pending")); + } + if (filters.includes("denied")) { + filterAggregates.push(eq(clients.approvalState, "denied")); + } + if (filters.includes("archived")) { + filterAggregates.push(eq(clients.archived, true)); + } + if (filters.includes("blocked")) { + filterAggregates.push(eq(clients.blocked, true)); + } + + conditions.push(or(...filterAggregates)); + } + const baseQuery = queryUserDevicesBase().where(and(...conditions)); const countQuery = db.$count(baseQuery.as("filtered_clients")); + const listDevicesQuery = baseQuery + .limit(pageSize) + .offset(pageSize * (page - 1)) + .orderBy( + sort_by + ? order === "asc" + ? asc(clients[sort_by]) + : desc(clients[sort_by]) + : asc(clients.clientId) + ); + const [clientsList, totalCount] = await Promise.all([ - baseQuery.limit(pageSize).offset(pageSize * (page - 1)), + listDevicesQuery, countQuery ]); From db6327c4ff1f60f59d564e0bd6b08d514bbf6a1e Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Sat, 7 Feb 2026 02:52:23 +0100 Subject: [PATCH 30/81] =?UTF-8?q?=F0=9F=94=87=20remove=20console.logs=20in?= =?UTF-8?q?=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/routers/client/listUserDevices.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/server/routers/client/listUserDevices.ts b/server/routers/client/listUserDevices.ts index 7ef5f784..0c07e40c 100644 --- a/server/routers/client/listUserDevices.ts +++ b/server/routers/client/listUserDevices.ts @@ -233,18 +233,6 @@ export async function listUserDevices( order } = parsedQuery.data; - console.log({ query: req.query }); - console.log({ - page, - pageSize, - query, - sort_by, - online, - filters, - agent, - order - }); - const parsedParams = listUserDevicesParamsSchema.safeParse(req.params); if (!parsedParams.success) { return next( From 5d7f082ebf2afdb8edc0914763b78fbc4cf4fa4c Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Sat, 7 Feb 2026 04:41:42 +0100 Subject: [PATCH 31/81] =?UTF-8?q?=E2=9C=A8=20sort=20user=20device=20table?= =?UTF-8?q?=20&=20refactor=20sort=20into=20common=20functino?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/routers/client/listUserDevices.ts | 58 +-- .../[orgId]/settings/clients/user/page.tsx | 17 +- src/components/ClientResourcesTable.tsx | 2 +- src/components/SitesTable.tsx | 74 ++-- src/components/UserDevicesTable.tsx | 396 ++++++++++-------- src/hooks/useSortColumn.ts | 56 --- src/lib/sortColumn.ts | 52 +++ 7 files changed, 366 insertions(+), 289 deletions(-) delete mode 100644 src/hooks/useSortColumn.ts create mode 100644 src/lib/sortColumn.ts diff --git a/server/routers/client/listUserDevices.ts b/server/routers/client/listUserDevices.ts index 0c07e40c..479d16a0 100644 --- a/server/routers/client/listUserDevices.ts +++ b/server/routers/client/listUserDevices.ts @@ -1,3 +1,4 @@ +import { build } from "@server/build"; import { clients, currentFingerprint, @@ -132,9 +133,12 @@ const listUserDevicesSchema = z.object({ ]) .optional() .catch(undefined), - filters: z.preprocess( - (val: string) => { - return val.split(","); // the search query array is an array joined by a comma + status: z.preprocess( + (val: string | undefined) => { + if (val) { + return val.split(","); // the search query array is an array joined by commas + } + return undefined; }, z .array( @@ -222,16 +226,8 @@ export async function listUserDevices( ) ); } - const { - page, - pageSize, - query, - sort_by, - online, - filters, - agent, - order - } = parsedQuery.data; + const { page, pageSize, query, sort_by, online, status, agent, order } = + parsedQuery.data; const parsedParams = listUserDevicesParamsSchema.safeParse(req.params); if (!parsedParams.success) { @@ -293,7 +289,7 @@ export async function listUserDevices( conditions.push( or( ilike(clients.name, "%" + query + "%"), - ilike(users.name, "%" + query + "%") + ilike(users.email, "%" + query + "%") ) ); } @@ -321,34 +317,40 @@ export async function listUserDevices( } } - if (filters.length > 0) { + if (status.length > 0) { const filterAggregates: (SQL | undefined)[] = []; - if (filters.includes("active")) { + if (status.includes("active")) { filterAggregates.push( and( eq(clients.archived, false), eq(clients.blocked, false), - or( - eq(clients.approvalState, "approved"), - isNull(clients.approvalState) // approval state of `NULL` means approved by default - ) + build !== "oss" + ? or( + eq(clients.approvalState, "approved"), + isNull(clients.approvalState) // approval state of `NULL` means approved by default + ) + : undefined // undefined are automatically ignored by `drizzle-orm` ) ); } - if (filters.includes("pending")) { - filterAggregates.push(eq(clients.approvalState, "pending")); - } - if (filters.includes("denied")) { - filterAggregates.push(eq(clients.approvalState, "denied")); - } - if (filters.includes("archived")) { + + if (status.includes("archived")) { filterAggregates.push(eq(clients.archived, true)); } - if (filters.includes("blocked")) { + if (status.includes("blocked")) { filterAggregates.push(eq(clients.blocked, true)); } + if (build !== "oss") { + if (status.includes("pending")) { + filterAggregates.push(eq(clients.approvalState, "pending")); + } + if (status.includes("denied")) { + filterAggregates.push(eq(clients.approvalState, "denied")); + } + } + conditions.push(or(...filterAggregates)); } diff --git a/src/app/[orgId]/settings/clients/user/page.tsx b/src/app/[orgId]/settings/clients/user/page.tsx index d047a60f..fcb24e4e 100644 --- a/src/app/[orgId]/settings/clients/user/page.tsx +++ b/src/app/[orgId]/settings/clients/user/page.tsx @@ -10,6 +10,7 @@ import { getTranslations } from "next-intl/server"; type ClientsPageProps = { params: Promise<{ orgId: string }>; + searchParams: Promise>; }; export const dynamic = "force-dynamic"; @@ -18,6 +19,7 @@ export default async function ClientsPage(props: ClientsPageProps) { const t = await getTranslations(); const params = await props.params; + const searchParams = new URLSearchParams(await props.searchParams); let userClients: ListUserDevicesResponse["devices"] = []; @@ -30,7 +32,10 @@ export default async function ClientsPage(props: ClientsPageProps) { try { const userRes = await internal.get< AxiosResponse - >(`/org/${params.orgId}/user-devices`, await authCookieHeader()); + >( + `/org/${params.orgId}/user-devices?${searchParams.toString()}`, + await authCookieHeader() + ); const responseData = userRes.data.data; userClients = responseData.devices; pagination = responseData.pagination; @@ -97,11 +102,6 @@ export default async function ClientsPage(props: ClientsPageProps) { const userClientRows: ClientRow[] = userClients.map(mapClientToRow); - console.log({ - userClientRows, - pagination - }); - return ( <> ); diff --git a/src/components/ClientResourcesTable.tsx b/src/components/ClientResourcesTable.tsx index e3fcd11a..126eb242 100644 --- a/src/components/ClientResourcesTable.tsx +++ b/src/components/ClientResourcesTable.tsx @@ -382,7 +382,7 @@ export default function ClientResourcesTable({ onPaginationChange={handlePaginationChange} pagination={pagination} rowCount={rowCount} - isRefreshing={isRefreshing} + isRefreshing={isRefreshing || isFiltering} enableColumnVisibility columnVisibility={{ niceId: false, diff --git a/src/components/SitesTable.tsx b/src/components/SitesTable.tsx index 76117776..c7857773 100644 --- a/src/components/SitesTable.tsx +++ b/src/components/SitesTable.tsx @@ -12,10 +12,10 @@ import { } from "@app/components/ui/dropdown-menu"; import { InfoPopup } from "@app/components/ui/info-popup"; import { useEnvContext } from "@app/hooks/useEnvContext"; -import { useSortColumn } from "@app/hooks/useSortColumn"; +import { useNavigationContext } from "@app/hooks/useNavigationContext"; +import { getNextSortOrder, getSortDirection } from "@app/lib/sortColumn"; import { toast } from "@app/hooks/useToast"; import { createApiClient, formatAxiosError } from "@app/lib/api"; -import { parseDataSize } from "@app/lib/dataSize"; import { build } from "@server/build"; import { type PaginationState } from "@tanstack/react-table"; import { @@ -24,21 +24,19 @@ import { ArrowUp10Icon, ArrowUpRight, ChevronsUpDownIcon, - Funnel, MoreHorizontal } from "lucide-react"; import { useTranslations } from "next-intl"; import Link from "next/link"; -import { usePathname, useRouter, useSearchParams } from "next/navigation"; +import { usePathname, useRouter } from "next/navigation"; import { useState, useTransition } from "react"; import { useDebouncedCallback } from "use-debounce"; +import z from "zod"; +import { ColumnFilterButton } from "./ColumnFilterButton"; import { ControlledDataTable, type ExtendedColumnDef } from "./ui/controlled-data-table"; -import { ColumnFilter } from "./ColumnFilter"; -import { ColumnFilterButton } from "./ColumnFilterButton"; -import z from "zod"; export type SiteRow = { id: number; @@ -71,16 +69,18 @@ export default function SitesTable({ rowCount }: SitesTableProps) { const router = useRouter(); - const searchParams = useSearchParams(); const pathname = usePathname(); + const { + navigate: filter, + isNavigating: isFiltering, + searchParams + } = useNavigationContext(); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const [selectedSite, setSelectedSite] = useState(null); const [isRefreshing, startTransition] = useTransition(); const [isNavigatingToAddPage, startNavigation] = useTransition(); - const [getSortDirection, toggleSorting] = useSortColumn(); - const api = createApiClient(useEnvContext()); const t = useTranslations(); @@ -136,9 +136,6 @@ export default function SitesTable({ }); } - const dataInOrder = getSortDirection("megabytesIn"); - const dataOutOrder = getSortDirection("megabytesOut"); - const columns: ExtendedColumnDef[] = [ { accessorKey: "name", @@ -212,6 +209,10 @@ export default function SitesTable({ accessorKey: "mbIn", friendlyName: t("dataIn"), header: () => { + const dataInOrder = getSortDirection( + "megabytesIn", + searchParams + ); const Icon = dataInOrder === "asc" ? ArrowDown01Icon @@ -221,21 +222,23 @@ export default function SitesTable({ return ( ); - }, - sortingFn: (rowA, rowB) => - parseDataSize(rowA.original.mbIn) - - parseDataSize(rowB.original.mbIn) + } }, { accessorKey: "mbOut", friendlyName: t("dataOut"), header: () => { + const dataOutOrder = getSortDirection( + "megabytesOut", + searchParams + ); + const Icon = dataOutOrder === "asc" ? ArrowDown01Icon @@ -245,16 +248,13 @@ export default function SitesTable({ return ( ); - }, - sortingFn: (rowA, rowB) => - parseDataSize(rowA.original.mbOut) - - parseDataSize(rowB.original.mbOut) + } }, { accessorKey: "type", @@ -423,18 +423,28 @@ export default function SitesTable({ } ]; + function toggleSort(column: string) { + const newSearch = getNextSortOrder(column, searchParams); + + filter({ + searchParams: newSearch + }); + } + const handlePaginationChange = (newPage: PaginationState) => { - const sp = new URLSearchParams(searchParams); - sp.set("page", (newPage.pageIndex + 1).toString()); - sp.set("pageSize", newPage.pageSize.toString()); - startTransition(() => router.push(`${pathname}?${sp.toString()}`)); + searchParams.set("page", (newPage.pageIndex + 1).toString()); + searchParams.set("pageSize", newPage.pageSize.toString()); + filter({ + searchParams + }); }; const handleSearchChange = useDebouncedCallback((query: string) => { - const sp = new URLSearchParams(searchParams); - sp.set("query", query); - sp.delete("page"); - startTransition(() => router.push(`${pathname}?${sp.toString()}`)); + searchParams.set("query", query); + searchParams.delete("page"); + filter({ + searchParams + }); }, 300); return ( @@ -478,7 +488,7 @@ export default function SitesTable({ onSearch={handleSearchChange} addButtonText={t("siteAdd")} onRefresh={refreshData} - isRefreshing={isRefreshing} + isRefreshing={isRefreshing || isFiltering} rowCount={rowCount} columnVisibility={{ niceId: false, diff --git a/src/components/UserDevicesTable.tsx b/src/components/UserDevicesTable.tsx index edc84088..7e441547 100644 --- a/src/components/UserDevicesTable.tsx +++ b/src/components/UserDevicesTable.tsx @@ -2,37 +2,41 @@ import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog"; import { Button } from "@app/components/ui/button"; -import { DataTable, ExtendedColumnDef } from "@app/components/ui/data-table"; +import { ExtendedColumnDef } from "@app/components/ui/data-table"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@app/components/ui/dropdown-menu"; +import { InfoPopup } from "@app/components/ui/info-popup"; import { useEnvContext } from "@app/hooks/useEnvContext"; +import { useNavigationContext } from "@app/hooks/useNavigationContext"; +import { getNextSortOrder, getSortDirection } from "@app/lib/sortColumn"; import { toast } from "@app/hooks/useToast"; import { createApiClient, formatAxiosError } from "@app/lib/api"; +import { formatFingerprintInfo } from "@app/lib/formatDeviceFingerprint"; import { getUserDisplayName } from "@app/lib/getUserDisplayName"; +import { build } from "@server/build"; +import type { PaginationState } from "@tanstack/react-table"; import { - formatFingerprintInfo, - formatPlatform -} from "@app/lib/formatDeviceFingerprint"; -import { + ArrowDown01Icon, ArrowRight, - ArrowUpDown, + ArrowUp10Icon, ArrowUpRight, - MoreHorizontal, - CircleSlash + ChevronsUpDownIcon, + CircleSlash, + MoreHorizontal } from "lucide-react"; import { useTranslations } from "next-intl"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { useMemo, useState, useTransition } from "react"; +import { useDebouncedCallback } from "use-debounce"; import ClientDownloadBanner from "./ClientDownloadBanner"; +import { ColumnFilterButton } from "./ColumnFilterButton"; import { Badge } from "./ui/badge"; -import { build } from "@server/build"; -import { usePaidStatus } from "@app/hooks/usePaidStatus"; -import { InfoPopup } from "@app/components/ui/info-popup"; +import { ControlledDataTable } from "./ui/controlled-data-table"; export type ClientRow = { id: number; @@ -68,9 +72,15 @@ export type ClientRow = { type ClientTableProps = { userClients: ClientRow[]; orgId: string; + pagination: PaginationState; + rowCount: number; }; -export default function UserDevicesTable({ userClients }: ClientTableProps) { +export default function UserDevicesTable({ + userClients, + pagination, + rowCount +}: ClientTableProps) { const router = useRouter(); const t = useTranslations(); @@ -80,6 +90,11 @@ export default function UserDevicesTable({ userClients }: ClientTableProps) { ); const api = createApiClient(useEnvContext()); + const { + navigate: filter, + isNavigating: isFiltering, + searchParams + } = useNavigationContext(); const [isRefreshing, startTransition] = useTransition(); const defaultUserColumnVisibility = { @@ -296,21 +311,7 @@ export default function UserDevicesTable({ userClients }: ClientTableProps) { accessorKey: "name", enableHiding: false, friendlyName: t("name"), - header: ({ column }) => { - return ( - - ); - }, + header: () => {t("name")}, cell: ({ row }) => { const r = row.original; const fingerprintInfo = r.fingerprint @@ -360,40 +361,12 @@ export default function UserDevicesTable({ userClients }: ClientTableProps) { { accessorKey: "niceId", friendlyName: t("identifier"), - header: ({ column }) => { - return ( - - ); - } + header: () => {t("identifier")} }, { accessorKey: "userEmail", friendlyName: t("users"), - header: ({ column }) => { - return ( - - ); - }, + header: () => {t("users")}, cell: ({ row }) => { const r = row.original; return r.userId ? ( @@ -416,19 +389,30 @@ export default function UserDevicesTable({ userClients }: ClientTableProps) { { accessorKey: "online", friendlyName: t("online"), - header: ({ column }) => { + header: () => { return ( - + onValueChange={(value) => + handleFilterChange("online", value) + } + searchPlaceholder={t("searchPlaceholder")} + emptyMessage={t("emptySearchOptions")} + label={t("online")} + className="p-3" + /> ); }, cell: ({ row }) => { @@ -453,18 +437,29 @@ export default function UserDevicesTable({ userClients }: ClientTableProps) { { accessorKey: "mbIn", friendlyName: t("dataIn"), - header: ({ column }) => { + header: () => { + const dataInOrder = getSortDirection( + "megabytesIn", + searchParams + ); + + console.log({ + dataInOrder, + searchParams: Object.fromEntries(searchParams.entries()) + }); + const Icon = + dataInOrder === "asc" + ? ArrowDown01Icon + : dataInOrder === "desc" + ? ArrowUp10Icon + : ChevronsUpDownIcon; return ( ); } @@ -472,18 +467,25 @@ export default function UserDevicesTable({ userClients }: ClientTableProps) { { accessorKey: "mbOut", friendlyName: t("dataOut"), - header: ({ column }) => { + header: () => { + const dataOutOrder = getSortDirection( + "megabytesOut", + searchParams + ); + + const Icon = + dataOutOrder === "asc" + ? ArrowDown01Icon + : dataOutOrder === "desc" + ? ArrowUp10Icon + : ChevronsUpDownIcon; return ( ); } @@ -491,21 +493,48 @@ export default function UserDevicesTable({ userClients }: ClientTableProps) { { accessorKey: "client", friendlyName: t("agent"), - header: ({ column }) => { - return ( - - ); - }, + ]} + selectedValue={searchParams.get("agent") ?? undefined} + onValueChange={(value) => + handleFilterChange("agent", value) + } + searchPlaceholder={t("searchPlaceholder")} + emptyMessage={t("emptySearchOptions")} + label={t("agent")} + className="p-3" + /> + ), cell: ({ row }) => { const originalRow = row.original; @@ -531,21 +560,7 @@ export default function UserDevicesTable({ userClients }: ClientTableProps) { { accessorKey: "subnet", friendlyName: t("address"), - header: ({ column }) => { - return ( - - ); - } + header: () => {t("address")} } ]; @@ -643,7 +658,7 @@ export default function UserDevicesTable({ userClients }: ClientTableProps) { }); return baseColumns; - }, [hasRowsWithoutUserId, t]); + }, [hasRowsWithoutUserId, t, getSortDirection, toggleSort]); const statusFilterOptions = useMemo(() => { const allOptions = [ @@ -691,6 +706,53 @@ export default function UserDevicesTable({ userClients }: ClientTableProps) { return ["active", "pending"]; }, []); + const [, setCount] = useState(0); + + function handleFilterChange( + column: string, + value: string | undefined | null | string[] + ) { + searchParams.delete(column); + searchParams.delete("page"); + + if (typeof value === "string") { + searchParams.set(column, value); + } else if (value) { + for (const val of value) { + searchParams.append(column, val); + } + } + + filter({ + searchParams + }); + setCount((c) => c + 1); + } + + function toggleSort(column: string) { + const newSearch = getNextSortOrder(column, searchParams); + + filter({ + searchParams: newSearch + }); + } + + const handlePaginationChange = (newPage: PaginationState) => { + searchParams.set("page", (newPage.pageIndex + 1).toString()); + searchParams.set("pageSize", newPage.pageSize.toString()); + filter({ + searchParams + }); + }; + + const handleSearchChange = useDebouncedCallback((query: string) => { + searchParams.set("query", query); + searchParams.delete("page"); + filter({ + searchParams + }); + }, 300); + return ( <> {selectedClient && !selectedClient.userId && ( @@ -714,67 +776,69 @@ export default function UserDevicesTable({ userClients }: ClientTableProps) { )} - { - if (selectedValues.length === 0) return true; - const rowArchived = row.archived; - const rowBlocked = row.blocked; - const approvalState = row.approvalState; - const isActive = - !rowArchived && - !rowBlocked && - approvalState !== "pending" && - approvalState !== "denied"; + // filters={[ + // { + // id: "status", + // label: t("status") || "Status", + // multiSelect: true, + // displayMode: "calculated", + // options: statusFilterOptions, + // filterFn: ( + // row: ClientRow, + // selectedValues: (string | number | boolean)[] + // ) => { + // if (selectedValues.length === 0) return true; + // const rowArchived = row.archived; + // const rowBlocked = row.blocked; + // const approvalState = row.approvalState; + // const isActive = + // !rowArchived && + // !rowBlocked && + // approvalState !== "pending" && + // approvalState !== "denied"; - if (selectedValues.includes("active") && isActive) - return true; - if ( - selectedValues.includes("pending") && - approvalState === "pending" - ) - return true; - if ( - selectedValues.includes("denied") && - approvalState === "denied" - ) - return true; - if ( - selectedValues.includes("archived") && - rowArchived - ) - return true; - if ( - selectedValues.includes("blocked") && - rowBlocked - ) - return true; - return false; - }, - defaultValues: statusFilterDefaultValues - } - ]} + // if (selectedValues.includes("active") && isActive) + // return true; + // if ( + // selectedValues.includes("pending") && + // approvalState === "pending" + // ) + // return true; + // if ( + // selectedValues.includes("denied") && + // approvalState === "denied" + // ) + // return true; + // if ( + // selectedValues.includes("archived") && + // rowArchived + // ) + // return true; + // if ( + // selectedValues.includes("blocked") && + // rowBlocked + // ) + // return true; + // return false; + // }, + // defaultValues: statusFilterDefaultValues + // } + // ]} /> ); diff --git a/src/hooks/useSortColumn.ts b/src/hooks/useSortColumn.ts deleted file mode 100644 index 95fb673e..00000000 --- a/src/hooks/useSortColumn.ts +++ /dev/null @@ -1,56 +0,0 @@ -import type { SortOrder } from "@app/lib/types/sort"; -import { useSearchParams, useRouter, usePathname } from "next/navigation"; -import { startTransition } from "react"; - -export function useSortColumn() { - const router = useRouter(); - const pathname = usePathname(); - const searchParams = useSearchParams(); - - const toggleSorting = (column: string) => { - const sp = new URLSearchParams(searchParams); - - let nextDirection: SortOrder = "indeterminate"; - - if (sp.get("sort_by") === column) { - nextDirection = (sp.get("order") as SortOrder) ?? "indeterminate"; - } - - switch (nextDirection) { - case "indeterminate": { - nextDirection = "asc"; - break; - } - case "asc": { - nextDirection = "desc"; - break; - } - default: { - nextDirection = "indeterminate"; - break; - } - } - - sp.delete("sort_by"); - sp.delete("order"); - - if (nextDirection !== "indeterminate") { - sp.set("sort_by", column); - sp.set("order", nextDirection); - } - - startTransition(() => router.push(`${pathname}?${sp.toString()}`)); - }; - - function getSortDirection(column: string) { - let currentDirection: SortOrder = "indeterminate"; - - if (searchParams.get("sort_by") === column) { - currentDirection = - (searchParams.get("order") as SortOrder) ?? "indeterminate"; - } - return currentDirection; - } - - return [getSortDirection, toggleSorting] as const; -} diff --git a/src/lib/sortColumn.ts b/src/lib/sortColumn.ts new file mode 100644 index 00000000..fcb4cc98 --- /dev/null +++ b/src/lib/sortColumn.ts @@ -0,0 +1,52 @@ +import type { SortOrder } from "@app/lib/types/sort"; + +export function getNextSortOrder( + column: string, + searchParams: URLSearchParams +) { + const sp = new URLSearchParams(searchParams); + + let nextDirection: SortOrder = "indeterminate"; + + if (sp.get("sort_by") === column) { + nextDirection = (sp.get("order") as SortOrder) ?? "indeterminate"; + } + + switch (nextDirection) { + case "indeterminate": { + nextDirection = "asc"; + break; + } + case "asc": { + nextDirection = "desc"; + break; + } + default: { + nextDirection = "indeterminate"; + break; + } + } + + sp.delete("sort_by"); + sp.delete("order"); + + if (nextDirection !== "indeterminate") { + sp.set("sort_by", column); + sp.set("order", nextDirection); + } + + return sp; +} + +export function getSortDirection( + column: string, + searchParams: URLSearchParams +) { + let currentDirection: SortOrder = "indeterminate"; + + if (searchParams.get("sort_by") === column) { + currentDirection = + (searchParams.get("order") as SortOrder) ?? "indeterminate"; + } + return currentDirection; +} From 1889386f647add7b16621c2da781f616bd32c8b6 Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Sat, 7 Feb 2026 04:51:37 +0100 Subject: [PATCH 32/81] =?UTF-8?q?=F0=9F=9A=A7=20wip:=20table=20filters?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/UserDevicesTable.tsx | 97 ++++++++++----------- src/components/ui/controlled-data-table.tsx | 20 ++--- 2 files changed, 54 insertions(+), 63 deletions(-) diff --git a/src/components/UserDevicesTable.tsx b/src/components/UserDevicesTable.tsx index 7e441547..e2fe6669 100644 --- a/src/components/UserDevicesTable.tsx +++ b/src/components/UserDevicesTable.tsx @@ -706,8 +706,6 @@ export default function UserDevicesTable({ return ["active", "pending"]; }, []); - const [, setCount] = useState(0); - function handleFilterChange( column: string, value: string | undefined | null | string[] @@ -726,7 +724,6 @@ export default function UserDevicesTable({ filter({ searchParams }); - setCount((c) => c + 1); } function toggleSort(column: string) { @@ -791,54 +788,54 @@ export default function UserDevicesTable({ rowCount={rowCount} stickyLeftColumn="name" stickyRightColumn="actions" - // filters={[ - // { - // id: "status", - // label: t("status") || "Status", - // multiSelect: true, - // displayMode: "calculated", - // options: statusFilterOptions, - // filterFn: ( - // row: ClientRow, - // selectedValues: (string | number | boolean)[] - // ) => { - // if (selectedValues.length === 0) return true; - // const rowArchived = row.archived; - // const rowBlocked = row.blocked; - // const approvalState = row.approvalState; - // const isActive = - // !rowArchived && - // !rowBlocked && - // approvalState !== "pending" && - // approvalState !== "denied"; + filters={[ + { + id: "status", + label: t("status") || "Status", + multiSelect: true, + displayMode: "calculated", + options: statusFilterOptions, + onFilter: ( + selectedValues: (string | number | boolean)[] + ) => { + console.log({ selectedValues }); + // if (selectedValues.length === 0) return true; + // const rowArchived = row.archived; + // const rowBlocked = row.blocked; + // const approvalState = row.approvalState; + // const isActive = + // !rowArchived && + // !rowBlocked && + // approvalState !== "pending" && + // approvalState !== "denied"; - // if (selectedValues.includes("active") && isActive) - // return true; - // if ( - // selectedValues.includes("pending") && - // approvalState === "pending" - // ) - // return true; - // if ( - // selectedValues.includes("denied") && - // approvalState === "denied" - // ) - // return true; - // if ( - // selectedValues.includes("archived") && - // rowArchived - // ) - // return true; - // if ( - // selectedValues.includes("blocked") && - // rowBlocked - // ) - // return true; - // return false; - // }, - // defaultValues: statusFilterDefaultValues - // } - // ]} + // if (selectedValues.includes("active") && isActive) + // return true; + // if ( + // selectedValues.includes("pending") && + // approvalState === "pending" + // ) + // return true; + // if ( + // selectedValues.includes("denied") && + // approvalState === "denied" + // ) + // return true; + // if ( + // selectedValues.includes("archived") && + // rowArchived + // ) + // return true; + // if ( + // selectedValues.includes("blocked") && + // rowBlocked + // ) + // return true; + // return false; + }, + values: statusFilterDefaultValues + } + ]} /> ); diff --git a/src/components/ui/controlled-data-table.tsx b/src/components/ui/controlled-data-table.tsx index 88f03384..996526e4 100644 --- a/src/components/ui/controlled-data-table.tsx +++ b/src/components/ui/controlled-data-table.tsx @@ -33,7 +33,7 @@ import { useStoredColumnVisibility } from "@app/hooks/useStoredColumnVisibility" import { Columns, Filter, Plus, RefreshCw, Search } from "lucide-react"; import { useTranslations } from "next-intl"; -import { useState } from "react"; +import { useMemo, useState } from "react"; // Extended ColumnDef type that includes optional friendlyName for column visibility dropdown export type ExtendedColumnDef = ColumnDef< @@ -54,11 +54,8 @@ type DataTableFilter = { label: string; options: FilterOption[]; multiSelect?: boolean; - filterFn: ( - row: any, - selectedValues: (string | number | boolean)[] - ) => boolean; - defaultValues?: (string | number | boolean)[]; + onFilter: (selectedValues: (string | number | boolean)[]) => void; + values?: (string | number | boolean)[]; displayMode?: "label" | "calculated"; // How to display the filter button text }; @@ -119,15 +116,13 @@ export function ControlledDataTable({ ); // TODO: filters - const [activeFilters, setActiveFilters] = useState< - Record - >(() => { + const activeFilters = useMemo(() => { const initial: Record = {}; filters?.forEach((filter) => { - initial[filter.id] = filter.defaultValues || []; + initial[filter.id] = filter.values || []; }); return initial; - }); + }, [filters]); console.log({ pagination, @@ -147,7 +142,6 @@ export function ControlledDataTable({ }, manualFiltering: true, manualPagination: true, - // pageCount: pagination.pageCount, rowCount, state: { columnFilters, @@ -177,7 +171,7 @@ export function ControlledDataTable({ } // Multiple selections: always join with "and" - return selectedOptions.map((opt) => opt.label).join(" and "); + return selectedOptions.map((opt) => opt.label).join(" or "); }; // Helper function to check if a column should be sticky From 577cb913433a8c713f12ebacc1a9f54f595f2bd9 Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Sat, 7 Feb 2026 05:37:01 +0100 Subject: [PATCH 33/81] =?UTF-8?q?=E2=9C=A8=20whole=20table=20filter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/UserDevicesTable.tsx | 52 +++++---------------- src/components/ui/controlled-data-table.tsx | 45 ++++++++++++++---- 2 files changed, 47 insertions(+), 50 deletions(-) diff --git a/src/components/UserDevicesTable.tsx b/src/components/UserDevicesTable.tsx index e2fe6669..7fe04ee4 100644 --- a/src/components/UserDevicesTable.tsx +++ b/src/components/UserDevicesTable.tsx @@ -699,16 +699,21 @@ export default function UserDevicesTable({ return allOptions; }, [t]); - const statusFilterDefaultValues = useMemo(() => { + const statusFilterValues = useMemo(() => { + const status = searchParams.getAll("status"); + if (status.length > 0) { + return status; + } + if (build === "oss") { return ["active"]; } return ["active", "pending"]; - }, []); + }, [searchParams]); function handleFilterChange( column: string, - value: string | undefined | null | string[] + value: string | null | undefined | string[] ) { searchParams.delete(column); searchParams.delete("page"); @@ -795,45 +800,10 @@ export default function UserDevicesTable({ multiSelect: true, displayMode: "calculated", options: statusFilterOptions, - onFilter: ( - selectedValues: (string | number | boolean)[] - ) => { - console.log({ selectedValues }); - // if (selectedValues.length === 0) return true; - // const rowArchived = row.archived; - // const rowBlocked = row.blocked; - // const approvalState = row.approvalState; - // const isActive = - // !rowArchived && - // !rowBlocked && - // approvalState !== "pending" && - // approvalState !== "denied"; - - // if (selectedValues.includes("active") && isActive) - // return true; - // if ( - // selectedValues.includes("pending") && - // approvalState === "pending" - // ) - // return true; - // if ( - // selectedValues.includes("denied") && - // approvalState === "denied" - // ) - // return true; - // if ( - // selectedValues.includes("archived") && - // rowArchived - // ) - // return true; - // if ( - // selectedValues.includes("blocked") && - // rowBlocked - // ) - // return true; - // return false; + onValueChange: (selectedValues: string[]) => { + handleFilterChange("status", selectedValues); }, - values: statusFilterDefaultValues + values: statusFilterValues } ]} /> diff --git a/src/components/ui/controlled-data-table.tsx b/src/components/ui/controlled-data-table.tsx index 996526e4..4b87a520 100644 --- a/src/components/ui/controlled-data-table.tsx +++ b/src/components/ui/controlled-data-table.tsx @@ -46,7 +46,7 @@ export type ExtendedColumnDef = ColumnDef< type FilterOption = { id: string; label: string; - value: string | number | boolean; + value: string; }; type DataTableFilter = { @@ -54,8 +54,8 @@ type DataTableFilter = { label: string; options: FilterOption[]; multiSelect?: boolean; - onFilter: (selectedValues: (string | number | boolean)[]) => void; - values?: (string | number | boolean)[]; + onValueChange: (selectedValues: string[]) => void; + values?: string[]; displayMode?: "label" | "calculated"; // How to display the filter button text }; @@ -117,7 +117,7 @@ export function ControlledDataTable({ // TODO: filters const activeFilters = useMemo(() => { - const initial: Record = {}; + const initial: Record = {}; filters?.forEach((filter) => { initial[filter.id] = filter.values || []; }); @@ -174,6 +174,33 @@ export function ControlledDataTable({ return selectedOptions.map((opt) => opt.label).join(" or "); }; + const handleFilterChange = ( + filterId: string, + optionValue: string, + checked: boolean + ) => { + const currentValues = activeFilters[filterId] || []; + const filter = filters?.find((f) => f.id === filterId); + + if (!filter) return; + + let newValues: string[]; + + if (filter.multiSelect) { + // Multi-select: add or remove the value + if (checked) { + newValues = [...currentValues, optionValue]; + } else { + newValues = currentValues.filter((v) => v !== optionValue); + } + } else { + // Single-select: replace the value + newValues = checked ? [optionValue] : []; + } + + filter.onValueChange(newValues); + }; + // Helper function to check if a column should be sticky const isStickyColumn = ( columnId: string | undefined, @@ -285,11 +312,11 @@ export function ControlledDataTable({ onCheckedChange={( checked ) => { - // handleFilterChange( - // filter.id, - // option.value, - // checked - // ) + handleFilterChange( + filter.id, + option.value, + checked + ); }} onSelect={(e) => e.preventDefault() From ff61b22e7e6afaced97fb7b62d0eda7a89d24d0a Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Sat, 7 Feb 2026 05:37:52 +0100 Subject: [PATCH 34/81] =?UTF-8?q?=E2=99=BB=EF=B8=8Fdo=20not=20set=20defaul?= =?UTF-8?q?t=20values?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/UserDevicesTable.tsx | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/components/UserDevicesTable.tsx b/src/components/UserDevicesTable.tsx index 7fe04ee4..642ef1f7 100644 --- a/src/components/UserDevicesTable.tsx +++ b/src/components/UserDevicesTable.tsx @@ -699,18 +699,6 @@ export default function UserDevicesTable({ return allOptions; }, [t]); - const statusFilterValues = useMemo(() => { - const status = searchParams.getAll("status"); - if (status.length > 0) { - return status; - } - - if (build === "oss") { - return ["active"]; - } - return ["active", "pending"]; - }, [searchParams]); - function handleFilterChange( column: string, value: string | null | undefined | string[] @@ -803,7 +791,7 @@ export default function UserDevicesTable({ onValueChange: (selectedValues: string[]) => { handleFilterChange("status", selectedValues); }, - values: statusFilterValues + values: searchParams.getAll("status") } ]} /> From aad060810a315fcaa4ef05591b909fe423ade0b3 Mon Sep 17 00:00:00 2001 From: Lokowitz Date: Sat, 7 Feb 2026 08:26:05 +0000 Subject: [PATCH 35/81] update package and move eslint to dev --- package-lock.json | 1452 ++++++++++++++++++++++++++++++++++----------- package.json | 18 +- 2 files changed, 1100 insertions(+), 370 deletions(-) diff --git a/package-lock.json b/package-lock.json index b3841ea3..cb91cb3e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,8 +10,8 @@ "license": "SEE LICENSE IN LICENSE AND README.md", "dependencies": { "@asteasolutions/zod-to-openapi": "8.4.0", - "@aws-sdk/client-s3": "3.983.0", - "@faker-js/faker": "10.2.0", + "@aws-sdk/client-s3": "3.985.0", + "@faker-js/faker": "10.3.0", "@headlessui/react": "2.2.9", "@hookform/resolvers": "5.2.2", "@monaco-editor/react": "4.7.0", @@ -57,8 +57,6 @@ "d3": "7.9.0", "date-fns": "4.1.0", "drizzle-orm": "0.45.1", - "eslint": "9.39.2", - "eslint-config-next": "16.1.6", "express": "5.2.1", "express-rate-limit": "8.2.1", "glob": "13.0.1", @@ -80,10 +78,10 @@ "nodemailer": "8.0.0", "oslo": "1.2.1", "pg": "8.18.0", - "posthog-node": "5.24.10", + "posthog-node": "5.24.11", "qrcode.react": "4.2.0", "react": "19.2.4", - "react-day-picker": "9.13.0", + "react-day-picker": "9.13.1", "react-dom": "19.2.4", "react-easy-sort": "1.8.0", "react-hook-form": "7.71.1", @@ -111,7 +109,7 @@ "devDependencies": { "@dotenvx/dotenvx": "1.52.0", "@esbuild-plugins/tsconfig-paths": "0.1.2", - "@react-email/preview-server": "5.2.7", + "@react-email/preview-server": "5.2.8", "@tailwindcss/postcss": "4.1.18", "@tanstack/react-query-devtools": "5.91.3", "@types/better-sqlite3": "7.6.13", @@ -137,11 +135,13 @@ "@types/yargs": "17.0.35", "babel-plugin-react-compiler": "1.0.0", "drizzle-kit": "0.31.8", - "esbuild": "0.27.2", + "esbuild": "0.27.3", "esbuild-node-externals": "1.20.1", + "eslint": "9.39.2", + "eslint-config-next": "16.1.6", "postcss": "8.5.6", "prettier": "3.8.1", - "react-email": "5.2.7", + "react-email": "5.2.8", "tailwindcss": "4.1.18", "tsc-alias": "1.8.16", "tsx": "4.21.0", @@ -166,6 +166,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -390,34 +391,34 @@ } }, "node_modules/@aws-sdk/client-s3": { - "version": "3.983.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.983.0.tgz", - "integrity": "sha512-V40PT2irPh3lj+Z95tZI6batVrjaTrWEOXRNVBuoZSgpM3Ak1jiE9ZXwVLkMcbb9/GH4xVpB3EsGM7gbxmgFLQ==", + "version": "3.985.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.985.0.tgz", + "integrity": "sha512-S9TqjzzZEEIKBnC7yFpvqM7CG9ALpY5qhQ5BnDBJtdG20NoGpjKLGUUfD2wmZItuhbrcM4Z8c6m6Fg0XYIOVvw==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.6", - "@aws-sdk/credential-provider-node": "^3.972.5", + "@aws-sdk/core": "^3.973.7", + "@aws-sdk/credential-provider-node": "^3.972.6", "@aws-sdk/middleware-bucket-endpoint": "^3.972.3", "@aws-sdk/middleware-expect-continue": "^3.972.3", - "@aws-sdk/middleware-flexible-checksums": "^3.972.4", + "@aws-sdk/middleware-flexible-checksums": "^3.972.5", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-location-constraint": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", - "@aws-sdk/middleware-sdk-s3": "^3.972.6", + "@aws-sdk/middleware-sdk-s3": "^3.972.7", "@aws-sdk/middleware-ssec": "^3.972.3", - "@aws-sdk/middleware-user-agent": "^3.972.6", + "@aws-sdk/middleware-user-agent": "^3.972.7", "@aws-sdk/region-config-resolver": "^3.972.3", - "@aws-sdk/signature-v4-multi-region": "3.983.0", + "@aws-sdk/signature-v4-multi-region": "3.985.0", "@aws-sdk/types": "^3.973.1", - "@aws-sdk/util-endpoints": "3.983.0", + "@aws-sdk/util-endpoints": "3.985.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", - "@aws-sdk/util-user-agent-node": "^3.972.4", + "@aws-sdk/util-user-agent-node": "^3.972.5", "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.22.0", + "@smithy/core": "^3.22.1", "@smithy/eventstream-serde-browser": "^4.2.8", "@smithy/eventstream-serde-config-resolver": "^4.3.8", "@smithy/eventstream-serde-node": "^4.2.8", @@ -428,25 +429,25 @@ "@smithy/invalid-dependency": "^4.2.8", "@smithy/md5-js": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.12", - "@smithy/middleware-retry": "^4.4.29", + "@smithy/middleware-endpoint": "^4.4.13", + "@smithy/middleware-retry": "^4.4.30", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", + "@smithy/node-http-handler": "^4.4.9", "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.11.1", + "@smithy/smithy-client": "^4.11.2", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.28", - "@smithy/util-defaults-mode-node": "^4.2.31", + "@smithy/util-defaults-mode-browser": "^4.3.29", + "@smithy/util-defaults-mode-node": "^4.2.32", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", - "@smithy/util-stream": "^4.5.10", + "@smithy/util-stream": "^4.5.11", "@smithy/util-utf8": "^4.2.0", "@smithy/util-waiter": "^4.2.8", "tslib": "^2.6.2" @@ -456,44 +457,44 @@ } }, "node_modules/@aws-sdk/client-sso": { - "version": "3.982.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.982.0.tgz", - "integrity": "sha512-qJrIiivmvujdGqJ0ldSUvhN3k3N7GtPesoOI1BSt0fNXovVnMz4C/JmnkhZihU7hJhDvxJaBROLYTU+lpild4w==", + "version": "3.985.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.985.0.tgz", + "integrity": "sha512-81J8iE8MuXhdbMfIz4sWFj64Pe41bFi/uqqmqOC5SlGv+kwoyLsyKS/rH2tW2t5buih4vTUxskRjxlqikTD4oQ==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.6", + "@aws-sdk/core": "^3.973.7", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", - "@aws-sdk/middleware-user-agent": "^3.972.6", + "@aws-sdk/middleware-user-agent": "^3.972.7", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/types": "^3.973.1", - "@aws-sdk/util-endpoints": "3.982.0", + "@aws-sdk/util-endpoints": "3.985.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", - "@aws-sdk/util-user-agent-node": "^3.972.4", + "@aws-sdk/util-user-agent-node": "^3.972.5", "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.22.0", + "@smithy/core": "^3.22.1", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.12", - "@smithy/middleware-retry": "^4.4.29", + "@smithy/middleware-endpoint": "^4.4.13", + "@smithy/middleware-retry": "^4.4.30", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", + "@smithy/node-http-handler": "^4.4.9", "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.11.1", + "@smithy/smithy-client": "^4.11.2", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.28", - "@smithy/util-defaults-mode-node": "^4.2.31", + "@smithy/util-defaults-mode-browser": "^4.3.29", + "@smithy/util-defaults-mode-node": "^4.2.32", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", @@ -504,36 +505,20 @@ "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/util-endpoints": { - "version": "3.982.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.982.0.tgz", - "integrity": "sha512-M27u8FJP7O0Of9hMWX5dipp//8iglmV9jr7R8SR8RveU+Z50/8TqH68Tu6wUWBGMfXjzbVwn1INIAO5lZrlxXQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.1", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-endpoints": "^3.2.8", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, "node_modules/@aws-sdk/core": { - "version": "3.973.6", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.6.tgz", - "integrity": "sha512-pz4ZOw3BLG0NdF25HoB9ymSYyPbMiIjwQJ2aROXRhAzt+b+EOxStfFv8s5iZyP6Kiw7aYhyWxj5G3NhmkoOTKw==", + "version": "3.973.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.7.tgz", + "integrity": "sha512-wNZZQQNlJ+hzD49cKdo+PY6rsTDElO8yDImnrI69p2PLBa7QomeUKAJWYp9xnaR38nlHqWhMHZuYLCQ3oSX+xg==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws-sdk/xml-builder": "^3.972.4", - "@smithy/core": "^3.22.0", + "@smithy/core": "^3.22.1", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", - "@smithy/smithy-client": "^4.11.1", + "@smithy/smithy-client": "^4.11.2", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.8", @@ -558,12 +543,12 @@ } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.972.4", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.4.tgz", - "integrity": "sha512-/8dnc7+XNMmViEom2xsNdArQxQPSgy4Z/lm6qaFPTrMFesT1bV3PsBhb19n09nmxHdrtQskYmViddUIjUQElXg==", + "version": "3.972.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.5.tgz", + "integrity": "sha512-LxJ9PEO4gKPXzkufvIESUysykPIdrV7+Ocb9yAhbhJLE4TiAYqbCVUE+VuKP1leGR1bBfjWjYgSV5MxprlX3mQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.6", + "@aws-sdk/core": "^3.973.7", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/types": "^4.12.0", @@ -574,20 +559,20 @@ } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.6", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.6.tgz", - "integrity": "sha512-5ERWqRljiZv44AIdvIRQ3k+EAV0Sq2WeJHvXuK7gL7bovSxOf8Al7MLH7Eh3rdovH4KHFnlIty7J71mzvQBl5Q==", + "version": "3.972.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.7.tgz", + "integrity": "sha512-L2uOGtvp2x3bTcxFTpSM+GkwFIPd8pHfGWO1764icMbo7e5xJh0nfhx1UwkXLnwvocTNEf8A7jISZLYjUSNaTg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.6", + "@aws-sdk/core": "^3.973.7", "@aws-sdk/types": "^3.973.1", "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/node-http-handler": "^4.4.8", + "@smithy/node-http-handler": "^4.4.9", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.11.1", + "@smithy/smithy-client": "^4.11.2", "@smithy/types": "^4.12.0", - "@smithy/util-stream": "^4.5.10", + "@smithy/util-stream": "^4.5.11", "tslib": "^2.6.2" }, "engines": { @@ -595,19 +580,19 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.972.4", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.4.tgz", - "integrity": "sha512-eRUg+3HaUKuXWn/lEMirdiA5HOKmEl8hEHVuszIDt2MMBUKgVX5XNGmb3XmbgU17h6DZ+RtjbxQpjhz3SbTjZg==", + "version": "3.972.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.5.tgz", + "integrity": "sha512-SdDTYE6jkARzOeL7+kudMIM4DaFnP5dZVeatzw849k4bSXDdErDS188bgeNzc/RA2WGrlEpsqHUKP6G7sVXhZg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.6", - "@aws-sdk/credential-provider-env": "^3.972.4", - "@aws-sdk/credential-provider-http": "^3.972.6", - "@aws-sdk/credential-provider-login": "^3.972.4", - "@aws-sdk/credential-provider-process": "^3.972.4", - "@aws-sdk/credential-provider-sso": "^3.972.4", - "@aws-sdk/credential-provider-web-identity": "^3.972.4", - "@aws-sdk/nested-clients": "3.982.0", + "@aws-sdk/core": "^3.973.7", + "@aws-sdk/credential-provider-env": "^3.972.5", + "@aws-sdk/credential-provider-http": "^3.972.7", + "@aws-sdk/credential-provider-login": "^3.972.5", + "@aws-sdk/credential-provider-process": "^3.972.5", + "@aws-sdk/credential-provider-sso": "^3.972.5", + "@aws-sdk/credential-provider-web-identity": "^3.972.5", + "@aws-sdk/nested-clients": "3.985.0", "@aws-sdk/types": "^3.973.1", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/property-provider": "^4.2.8", @@ -620,13 +605,13 @@ } }, "node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.4", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.4.tgz", - "integrity": "sha512-nLGjXuvWWDlQAp505xIONI7Gam0vw2p7Qu3P6on/W2q7rjJXtYjtpHbcsaOjJ/pAju3eTvEQuSuRedcRHVQIAQ==", + "version": "3.972.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.5.tgz", + "integrity": "sha512-uYq1ILyTSI6ZDCMY5+vUsRM0SOCVI7kaW4wBrehVVkhAxC6y+e9rvGtnoZqCOWL1gKjTMouvsf4Ilhc5NCg1Aw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.6", - "@aws-sdk/nested-clients": "3.982.0", + "@aws-sdk/core": "^3.973.7", + "@aws-sdk/nested-clients": "3.985.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", @@ -639,17 +624,17 @@ } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.5", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.5.tgz", - "integrity": "sha512-VWXKgSISQCI2GKN3zakTNHSiZ0+mux7v6YHmmbLQp/o3fvYUQJmKGcLZZzg2GFA+tGGBStplra9VFNf/WwxpYg==", + "version": "3.972.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.6.tgz", + "integrity": "sha512-DZ3CnAAtSVtVz+G+ogqecaErMLgzph4JH5nYbHoBMgBkwTUV+SUcjsjOJwdBJTHu3Dm6l5LBYekZoU2nDqQk2A==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "^3.972.4", - "@aws-sdk/credential-provider-http": "^3.972.6", - "@aws-sdk/credential-provider-ini": "^3.972.4", - "@aws-sdk/credential-provider-process": "^3.972.4", - "@aws-sdk/credential-provider-sso": "^3.972.4", - "@aws-sdk/credential-provider-web-identity": "^3.972.4", + "@aws-sdk/credential-provider-env": "^3.972.5", + "@aws-sdk/credential-provider-http": "^3.972.7", + "@aws-sdk/credential-provider-ini": "^3.972.5", + "@aws-sdk/credential-provider-process": "^3.972.5", + "@aws-sdk/credential-provider-sso": "^3.972.5", + "@aws-sdk/credential-provider-web-identity": "^3.972.5", "@aws-sdk/types": "^3.973.1", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/property-provider": "^4.2.8", @@ -662,12 +647,12 @@ } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.972.4", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.4.tgz", - "integrity": "sha512-TCZpWUnBQN1YPk6grvd5x419OfXjHvhj5Oj44GYb84dOVChpg/+2VoEj+YVA4F4E/6huQPNnX7UYbTtxJqgihw==", + "version": "3.972.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.5.tgz", + "integrity": "sha512-HDKF3mVbLnuqGg6dMnzBf1VUOywE12/N286msI9YaK9mEIzdsGCtLTvrDhe3Up0R9/hGFbB+9l21/TwF5L1C6g==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.6", + "@aws-sdk/core": "^3.973.7", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", @@ -679,14 +664,14 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.972.4", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.4.tgz", - "integrity": "sha512-wzsGwv9mKlwJ3vHLyembBvGE/5nPUIwRR2I51B1cBV4Cb4ql9nIIfpmHzm050XYTY5fqTOKJQnhLj7zj89VG8g==", + "version": "3.972.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.5.tgz", + "integrity": "sha512-8urj3AoeNeQisjMmMBhFeiY2gxt6/7wQQbEGun0YV/OaOOiXrIudTIEYF8ZfD+NQI6X1FY5AkRsx6O/CaGiybA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-sso": "3.982.0", - "@aws-sdk/core": "^3.973.6", - "@aws-sdk/token-providers": "3.982.0", + "@aws-sdk/client-sso": "3.985.0", + "@aws-sdk/core": "^3.973.7", + "@aws-sdk/token-providers": "3.985.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", @@ -698,13 +683,13 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.972.4", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.4.tgz", - "integrity": "sha512-hIzw2XzrG8jzsUSEatehmpkd5rWzASg5IHUfA+m01k/RtvfAML7ZJVVohuKdhAYx+wV2AThLiQJVzqn7F0khrw==", + "version": "3.972.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.5.tgz", + "integrity": "sha512-OK3cULuJl6c+RcDZfPpaK5o3deTOnKZbxm7pzhFNGA3fI2hF9yDih17fGRazJzGGWaDVlR9ejZrpDef4DJCEsw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.6", - "@aws-sdk/nested-clients": "3.982.0", + "@aws-sdk/core": "^3.973.7", + "@aws-sdk/nested-clients": "3.985.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", @@ -749,15 +734,15 @@ } }, "node_modules/@aws-sdk/middleware-flexible-checksums": { - "version": "3.972.4", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.972.4.tgz", - "integrity": "sha512-xOxsUkF3O3BtIe3tf54OpPo94eZepjFm3z0Dd2TZKbsPxMiRTFXurC04wJ58o/wPW9YHVO9VqZik3MfoPfrKlw==", + "version": "3.972.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.972.5.tgz", + "integrity": "sha512-SF/1MYWx67OyCrLA4icIpWUfCkdlOi8Y1KecQ9xYxkL10GMjVdPTGPnYhAg0dw5U43Y9PVUWhAV2ezOaG+0BLg==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", "@aws-crypto/util": "5.2.0", - "@aws-sdk/core": "^3.973.6", + "@aws-sdk/core": "^3.973.7", "@aws-sdk/crc64-nvme": "3.972.0", "@aws-sdk/types": "^3.973.1", "@smithy/is-array-buffer": "^4.2.0", @@ -765,7 +750,7 @@ "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "@smithy/util-middleware": "^4.2.8", - "@smithy/util-stream": "^4.5.10", + "@smithy/util-stream": "^4.5.11", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, @@ -833,23 +818,23 @@ } }, "node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.972.6", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.6.tgz", - "integrity": "sha512-Xq7wM6kbgJN1UO++8dvH/efPb1nTwWqFCpZCR7RCLOETP7xAUAhVo7JmsCnML5Di/iC4Oo5VrJ4QmkYcMZniLw==", + "version": "3.972.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.7.tgz", + "integrity": "sha512-VtZ7tMIw18VzjG+I6D6rh2eLkJfTtByiFoCIauGDtTTPBEUMQUiGaJ/zZrPlCY6BsvLLeFKz3+E5mntgiOWmIg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.6", + "@aws-sdk/core": "^3.973.7", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-arn-parser": "^3.972.2", - "@smithy/core": "^3.22.0", + "@smithy/core": "^3.22.1", "@smithy/node-config-provider": "^4.3.8", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", - "@smithy/smithy-client": "^4.11.1", + "@smithy/smithy-client": "^4.11.2", "@smithy/types": "^4.12.0", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-middleware": "^4.2.8", - "@smithy/util-stream": "^4.5.10", + "@smithy/util-stream": "^4.5.11", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, @@ -872,15 +857,15 @@ } }, "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.972.6", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.6.tgz", - "integrity": "sha512-TehLN8W/kivl0U9HcS+keryElEWORROpghDXZBLfnb40DXM7hx/i+7OOjkogXQOF3QtUraJVRkHQ07bPhrWKlw==", + "version": "3.972.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.7.tgz", + "integrity": "sha512-HUD+geASjXSCyL/DHPQc/Ua7JhldTcIglVAoCV8kiVm99IaFSlAbTvEnyhZwdE6bdFyTL+uIaWLaCFSRsglZBQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.6", + "@aws-sdk/core": "^3.973.7", "@aws-sdk/types": "^3.973.1", - "@aws-sdk/util-endpoints": "3.982.0", - "@smithy/core": "^3.22.0", + "@aws-sdk/util-endpoints": "3.985.0", + "@smithy/core": "^3.22.1", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" @@ -889,61 +874,45 @@ "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/middleware-user-agent/node_modules/@aws-sdk/util-endpoints": { - "version": "3.982.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.982.0.tgz", - "integrity": "sha512-M27u8FJP7O0Of9hMWX5dipp//8iglmV9jr7R8SR8RveU+Z50/8TqH68Tu6wUWBGMfXjzbVwn1INIAO5lZrlxXQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.1", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-endpoints": "^3.2.8", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, "node_modules/@aws-sdk/nested-clients": { - "version": "3.982.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.982.0.tgz", - "integrity": "sha512-VVkaH27digrJfdVrT64rjkllvOp4oRiZuuJvrylLXAKl18ujToJR7AqpDldL/LS63RVne3QWIpkygIymxFtliQ==", + "version": "3.985.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.985.0.tgz", + "integrity": "sha512-TsWwKzb/2WHafAY0CE7uXgLj0FmnkBTgfioG9HO+7z/zCPcl1+YU+i7dW4o0y+aFxFgxTMG+ExBQpqT/k2ao8g==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.6", + "@aws-sdk/core": "^3.973.7", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", - "@aws-sdk/middleware-user-agent": "^3.972.6", + "@aws-sdk/middleware-user-agent": "^3.972.7", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/types": "^3.973.1", - "@aws-sdk/util-endpoints": "3.982.0", + "@aws-sdk/util-endpoints": "3.985.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", - "@aws-sdk/util-user-agent-node": "^3.972.4", + "@aws-sdk/util-user-agent-node": "^3.972.5", "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.22.0", + "@smithy/core": "^3.22.1", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.12", - "@smithy/middleware-retry": "^4.4.29", + "@smithy/middleware-endpoint": "^4.4.13", + "@smithy/middleware-retry": "^4.4.30", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", + "@smithy/node-http-handler": "^4.4.9", "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.11.1", + "@smithy/smithy-client": "^4.11.2", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.28", - "@smithy/util-defaults-mode-node": "^4.2.31", + "@smithy/util-defaults-mode-browser": "^4.3.29", + "@smithy/util-defaults-mode-node": "^4.2.32", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", @@ -954,22 +923,6 @@ "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/util-endpoints": { - "version": "3.982.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.982.0.tgz", - "integrity": "sha512-M27u8FJP7O0Of9hMWX5dipp//8iglmV9jr7R8SR8RveU+Z50/8TqH68Tu6wUWBGMfXjzbVwn1INIAO5lZrlxXQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.1", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-endpoints": "^3.2.8", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, "node_modules/@aws-sdk/region-config-resolver": { "version": "3.972.3", "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.3.tgz", @@ -987,12 +940,12 @@ } }, "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.983.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.983.0.tgz", - "integrity": "sha512-11FCcxI/WKRufKDdPgKPXtrhjDArhkOPb4mf66rICZUnPHlD8Cb7cjZZS/eFC+iuwoHBosrxo0hYsvK3s7DxGw==", + "version": "3.985.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.985.0.tgz", + "integrity": "sha512-W6hTSOPiSbh4IdTYVxN7xHjpCh0qvfQU1GKGBzGQm0ZEIOaMmWqiDEvFfyGYKmfBvumT8vHKxQRTX0av9omtIg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-sdk-s3": "^3.972.6", + "@aws-sdk/middleware-sdk-s3": "^3.972.7", "@aws-sdk/types": "^3.973.1", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", @@ -1004,13 +957,13 @@ } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.982.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.982.0.tgz", - "integrity": "sha512-v3M0KYp2TVHYHNBT7jHD9lLTWAdS9CaWJ2jboRKt0WAB65bA7iUEpR+k4VqKYtpQN4+8kKSc4w+K6kUNZkHKQw==", + "version": "3.985.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.985.0.tgz", + "integrity": "sha512-+hwpHZyEq8k+9JL2PkE60V93v2kNhUIv7STFt+EAez1UJsJOQDhc5LpzEX66pNjclI5OTwBROs/DhJjC/BtMjQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.6", - "@aws-sdk/nested-clients": "3.982.0", + "@aws-sdk/core": "^3.973.7", + "@aws-sdk/nested-clients": "3.985.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", @@ -1047,9 +1000,9 @@ } }, "node_modules/@aws-sdk/util-endpoints": { - "version": "3.983.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.983.0.tgz", - "integrity": "sha512-t/VbL2X3gvDEjC4gdySOeFFOZGQEBKwa23pRHeB7hBLBZ119BB/2OEFtTFWKyp3bnMQgxpeVeGS7/hxk6wpKJw==", + "version": "3.985.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.985.0.tgz", + "integrity": "sha512-vth7UfGSUR3ljvaq8V4Rc62FsM7GUTH/myxPWkaEgOrprz1/Pc72EgTXxj+cPPPDAfHFIpjhkB7T7Td0RJx+BA==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "^3.973.1", @@ -1087,12 +1040,12 @@ } }, "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.972.4", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.972.4.tgz", - "integrity": "sha512-3WFCBLiM8QiHDfosQq3Py+lIMgWlFWwFQliUHUqwEiRqLnKyhgbU3AKa7AWJF7lW2Oc/2kFNY4MlAYVnVc0i8A==", + "version": "3.972.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.972.5.tgz", + "integrity": "sha512-GsUDF+rXyxDZkkJxUsDxnA67FG+kc5W1dnloCFLl6fWzceevsCYzJpASBzT+BPjwUgREE6FngfJYYYMQUY5fZQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-user-agent": "^3.972.6", + "@aws-sdk/middleware-user-agent": "^3.972.7", "@aws-sdk/types": "^3.973.1", "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", @@ -1137,6 +1090,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", @@ -1151,6 +1105,7 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -1160,6 +1115,7 @@ "version": "7.26.10", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz", "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", + "dev": true, "license": "MIT", "peer": true, "dependencies": { @@ -1191,6 +1147,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -1200,6 +1157,7 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.28.5", @@ -1216,6 +1174,7 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.28.5" @@ -1231,6 +1190,7 @@ "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/compat-data": "^7.27.2", @@ -1247,6 +1207,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -1256,6 +1217,7 @@ "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -1265,6 +1227,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, "license": "MIT", "dependencies": { "@babel/traverse": "^7.27.1", @@ -1278,6 +1241,7 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.28.5" @@ -1293,6 +1257,7 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", @@ -1311,6 +1276,7 @@ "version": "7.28.3", "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.27.1", @@ -1328,6 +1294,7 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.28.5" @@ -1343,6 +1310,7 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", @@ -1361,6 +1329,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "devOptional": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -1370,6 +1339,7 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "devOptional": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -1379,6 +1349,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -1388,6 +1359,7 @@ "version": "7.28.4", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "dev": true, "license": "MIT", "dependencies": { "@babel/template": "^7.27.2", @@ -1401,6 +1373,7 @@ "version": "7.27.0", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.27.0" @@ -1425,6 +1398,7 @@ "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", @@ -1439,6 +1413,7 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.28.5" @@ -1454,6 +1429,7 @@ "version": "7.27.0", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz", "integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.26.2", @@ -1472,6 +1448,7 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -2037,9 +2014,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", - "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", "cpu": [ "ppc64" ], @@ -2054,9 +2031,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", - "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", "cpu": [ "arm" ], @@ -2071,9 +2048,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", - "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", "cpu": [ "arm64" ], @@ -2088,9 +2065,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", - "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", "cpu": [ "x64" ], @@ -2105,9 +2082,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", - "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", "cpu": [ "arm64" ], @@ -2122,9 +2099,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", - "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", "cpu": [ "x64" ], @@ -2139,9 +2116,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", - "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", "cpu": [ "arm64" ], @@ -2156,9 +2133,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", - "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", "cpu": [ "x64" ], @@ -2173,9 +2150,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", - "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", "cpu": [ "arm" ], @@ -2190,9 +2167,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", - "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", "cpu": [ "arm64" ], @@ -2207,9 +2184,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", - "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", "cpu": [ "ia32" ], @@ -2224,9 +2201,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", - "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", "cpu": [ "loong64" ], @@ -2241,9 +2218,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", - "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", "cpu": [ "mips64el" ], @@ -2258,9 +2235,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", - "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", "cpu": [ "ppc64" ], @@ -2275,9 +2252,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", - "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", "cpu": [ "riscv64" ], @@ -2292,9 +2269,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", - "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", "cpu": [ "s390x" ], @@ -2309,9 +2286,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", - "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", "cpu": [ "x64" ], @@ -2326,9 +2303,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", - "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", "cpu": [ "arm64" ], @@ -2343,9 +2320,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", - "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", "cpu": [ "x64" ], @@ -2360,9 +2337,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", - "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", "cpu": [ "arm64" ], @@ -2377,9 +2354,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", - "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", "cpu": [ "x64" ], @@ -2394,9 +2371,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", - "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", "cpu": [ "arm64" ], @@ -2411,9 +2388,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", - "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", "cpu": [ "x64" ], @@ -2428,9 +2405,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", - "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", "cpu": [ "arm64" ], @@ -2445,9 +2422,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", - "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", "cpu": [ "ia32" ], @@ -2462,9 +2439,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", - "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", "cpu": [ "x64" ], @@ -2482,6 +2459,7 @@ "version": "4.9.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, "license": "MIT", "dependencies": { "eslint-visitor-keys": "^3.4.3" @@ -2500,6 +2478,7 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -2512,6 +2491,7 @@ "version": "4.12.2", "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" @@ -2521,6 +2501,7 @@ "version": "0.21.1", "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@eslint/object-schema": "^2.1.7", @@ -2535,6 +2516,7 @@ "version": "0.4.2", "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@eslint/core": "^0.17.0" @@ -2547,6 +2529,7 @@ "version": "0.17.0", "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@types/json-schema": "^7.0.15" @@ -2559,6 +2542,7 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", + "dev": true, "license": "MIT", "dependencies": { "ajv": "^6.12.4", @@ -2582,6 +2566,7 @@ "version": "14.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -2594,6 +2579,7 @@ "version": "9.39.2", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", + "dev": true, "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2606,6 +2592,7 @@ "version": "2.1.7", "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2615,6 +2602,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@eslint/core": "^0.17.0", @@ -2625,9 +2613,9 @@ } }, "node_modules/@faker-js/faker": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-10.2.0.tgz", - "integrity": "sha512-rTXwAsIxpCqzUnZvrxVh3L0QA0NzToqWBLAhV+zDV3MIIwiQhAZHMdPCIaj5n/yADu/tyk12wIPgL6YHGXJP+g==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-10.3.0.tgz", + "integrity": "sha512-It0Sne6P3szg7JIi6CgKbvTZoMjxBZhcv91ZrqrNuaZQfB5WoqYYbzCUOq89YR+VY8juY9M1vDWmDDa2TzfXCw==", "funding": [ { "type": "opencollective", @@ -2796,6 +2784,7 @@ "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=18.18.0" @@ -2805,6 +2794,7 @@ "version": "0.16.7", "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@humanfs/core": "^0.19.1", @@ -2818,6 +2808,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=12.22" @@ -2831,6 +2822,7 @@ "version": "0.4.3", "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=18.18" @@ -3339,6 +3331,7 @@ "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", @@ -3360,6 +3353,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -3369,12 +3363,14 @@ "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.31", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -3432,6 +3428,7 @@ "version": "16.1.6", "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.1.6.tgz", "integrity": "sha512-/Qq3PTagA6+nYVfryAtQ7/9FEr/6YVyvOtl6rZnGsbReGLf0jZU6gkpr1FuChAQpvV46a78p4cmHOVP8mbfSMQ==", + "dev": true, "license": "MIT", "dependencies": { "fast-glob": "3.3.1" @@ -4147,6 +4144,7 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", @@ -4160,6 +4158,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -4169,6 +4168,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", @@ -4182,6 +4182,7 @@ "version": "1.0.39", "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", + "dev": true, "license": "MIT", "engines": { "node": ">=12.4.0" @@ -4700,9 +4701,9 @@ } }, "node_modules/@posthog/core": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/@posthog/core/-/core-1.20.0.tgz", - "integrity": "sha512-e/F20we0t6bPMuDOVOe53f908s23vuKEpFKNXmZcx4bSYsPkjRN49akIIHU621HBJdcsFx537vhJYKZxu8uS9w==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/@posthog/core/-/core-1.20.1.tgz", + "integrity": "sha512-uoTmWkYCtLYFpiK37/JCq+BuCA/OZn1qQZn5cPv1EEKt3ni3Zgg48xWCnSEyGFl5KKSXlfCruiRTwnbAtCgrBA==", "license": "MIT", "dependencies": { "cross-spawn": "^7.0.6" @@ -7164,26 +7165,469 @@ } }, "node_modules/@react-email/preview-server": { - "version": "5.2.7", - "resolved": "https://registry.npmjs.org/@react-email/preview-server/-/preview-server-5.2.7.tgz", - "integrity": "sha512-3ueOHitbbrbtclkieghHJupwoFFNMGQABBLCTIHFmyY6Gcg2CK7drwFzTFyPd7M756SvmD0E0lTDtoRkSTqrJg==", + "version": "5.2.8", + "resolved": "https://registry.npmjs.org/@react-email/preview-server/-/preview-server-5.2.8.tgz", + "integrity": "sha512-drQ0C7vi7P0uE7Ox1Cyiujsx0oqp2RbIscOdSBR5qvzw3EKjlGbW2pWjQ000cjxTq3Si7lqlRKhOIF8MzOnqHw==", "dev": true, "license": "MIT", "dependencies": { - "next": "16.0.11" + "esbuild": "0.25.10", + "next": "16.1.6" + } + }, + "node_modules/@react-email/preview-server/node_modules/@esbuild/aix-ppc64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.10.tgz", + "integrity": "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-email/preview-server/node_modules/@esbuild/android-arm": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.10.tgz", + "integrity": "sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-email/preview-server/node_modules/@esbuild/android-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.10.tgz", + "integrity": "sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-email/preview-server/node_modules/@esbuild/android-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.10.tgz", + "integrity": "sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-email/preview-server/node_modules/@esbuild/darwin-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.10.tgz", + "integrity": "sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-email/preview-server/node_modules/@esbuild/darwin-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.10.tgz", + "integrity": "sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-email/preview-server/node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.10.tgz", + "integrity": "sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-email/preview-server/node_modules/@esbuild/freebsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.10.tgz", + "integrity": "sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-email/preview-server/node_modules/@esbuild/linux-arm": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.10.tgz", + "integrity": "sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-email/preview-server/node_modules/@esbuild/linux-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.10.tgz", + "integrity": "sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-email/preview-server/node_modules/@esbuild/linux-ia32": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.10.tgz", + "integrity": "sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-email/preview-server/node_modules/@esbuild/linux-loong64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.10.tgz", + "integrity": "sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-email/preview-server/node_modules/@esbuild/linux-mips64el": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.10.tgz", + "integrity": "sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-email/preview-server/node_modules/@esbuild/linux-ppc64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.10.tgz", + "integrity": "sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-email/preview-server/node_modules/@esbuild/linux-riscv64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.10.tgz", + "integrity": "sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-email/preview-server/node_modules/@esbuild/linux-s390x": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.10.tgz", + "integrity": "sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-email/preview-server/node_modules/@esbuild/linux-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.10.tgz", + "integrity": "sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-email/preview-server/node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.10.tgz", + "integrity": "sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-email/preview-server/node_modules/@esbuild/netbsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.10.tgz", + "integrity": "sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-email/preview-server/node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.10.tgz", + "integrity": "sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-email/preview-server/node_modules/@esbuild/openbsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.10.tgz", + "integrity": "sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-email/preview-server/node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.10.tgz", + "integrity": "sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-email/preview-server/node_modules/@esbuild/sunos-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.10.tgz", + "integrity": "sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-email/preview-server/node_modules/@esbuild/win32-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.10.tgz", + "integrity": "sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-email/preview-server/node_modules/@esbuild/win32-ia32": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.10.tgz", + "integrity": "sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-email/preview-server/node_modules/@esbuild/win32-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.10.tgz", + "integrity": "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" } }, "node_modules/@react-email/preview-server/node_modules/@next/env": { - "version": "16.0.11", - "resolved": "https://registry.npmjs.org/@next/env/-/env-16.0.11.tgz", - "integrity": "sha512-hULMheQaOhFK1vAoFPigXca42LguwyLILtJKPRzpY1d+og6jk0YNAQVwLGNYYhWEMd2zj4gcIWSf1yC5PffqqA==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.1.6.tgz", + "integrity": "sha512-N1ySLuZjnAtN3kFnwhAwPvZah8RJxKasD7x1f8shFqhncnWZn4JMfg37diLNuoHsLAlrDfM3g4mawVdtAG8XLQ==", "dev": true, "license": "MIT" }, "node_modules/@react-email/preview-server/node_modules/@next/swc-darwin-arm64": { - "version": "16.0.11", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.0.11.tgz", - "integrity": "sha512-3G7Rx6m6tgLqkc3Ce3QY/Yrsx7nJF4ithdHfx70Jmzel8m2xpjnGRC+oB4UcCHvQwN0ZP5YsLJakwx/M0vWbSQ==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.1.6.tgz", + "integrity": "sha512-wTzYulosJr/6nFnqGW7FrG3jfUUlEf8UjGA0/pyypJl42ExdVgC6xJgcXQ+V8QFn6niSG2Pb8+MIG1mZr2vczw==", "cpu": [ "arm64" ], @@ -7198,9 +7642,9 @@ } }, "node_modules/@react-email/preview-server/node_modules/@next/swc-darwin-x64": { - "version": "16.0.11", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.0.11.tgz", - "integrity": "sha512-poUTsYKRwuG+eApDngouEiN6AGcAMq8TAQYP8Nou7iMS7x6+q3dFhhyhgodIzTF9acsEINl4cIzMaM9XJor8kw==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.1.6.tgz", + "integrity": "sha512-BLFPYPDO+MNJsiDWbeVzqvYd4NyuRrEYVB5k2N3JfWncuHAy2IVwMAOlVQDFjj+krkWzhY2apvmekMkfQR0CUQ==", "cpu": [ "x64" ], @@ -7215,9 +7659,9 @@ } }, "node_modules/@react-email/preview-server/node_modules/@next/swc-linux-arm64-gnu": { - "version": "16.0.11", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.0.11.tgz", - "integrity": "sha512-Q9shvB+eLNrK/n8w+/ZTWSzbEIzJ56mP83ZVaqmHay6/Ulcn6THEId4gxfYCXmSwEG/xPAtv58FBWeZkp36XUA==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.1.6.tgz", + "integrity": "sha512-OJYkCd5pj/QloBvoEcJ2XiMnlJkRv9idWA/j0ugSuA34gMT6f5b7vOiCQHVRpvStoZUknhl6/UxOXL4OwtdaBw==", "cpu": [ "arm64" ], @@ -7232,9 +7676,9 @@ } }, "node_modules/@react-email/preview-server/node_modules/@next/swc-linux-arm64-musl": { - "version": "16.0.11", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.0.11.tgz", - "integrity": "sha512-rq+d/a0FZHVPEh3zismoQgfVkSIEzlTbNhD4Z8bToLMszUlggAh1D1syhJ4MHkYzXRszhjS2emy0PYXz7Uwttw==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.1.6.tgz", + "integrity": "sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ==", "cpu": [ "arm64" ], @@ -7249,9 +7693,9 @@ } }, "node_modules/@react-email/preview-server/node_modules/@next/swc-linux-x64-gnu": { - "version": "16.0.11", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.0.11.tgz", - "integrity": "sha512-82Wroterii1p15O+ZF/DDsHPuxKptR1JGK+obgbAk13vrc3B/fTJ2qOOmdeoMwAQ15gb/9mN4LQl9+IzFje76Q==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.1.6.tgz", + "integrity": "sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ==", "cpu": [ "x64" ], @@ -7266,9 +7710,9 @@ } }, "node_modules/@react-email/preview-server/node_modules/@next/swc-linux-x64-musl": { - "version": "16.0.11", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.0.11.tgz", - "integrity": "sha512-YK9RoeZuHWBd+wHi5/7VLp6P5ZOldAjQfBjjtzcR4f14FNmwT0a3ozMMlG2txDxh53krAd5yOO601RbJxH0gCQ==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.1.6.tgz", + "integrity": "sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg==", "cpu": [ "x64" ], @@ -7283,9 +7727,9 @@ } }, "node_modules/@react-email/preview-server/node_modules/@next/swc-win32-arm64-msvc": { - "version": "16.0.11", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.0.11.tgz", - "integrity": "sha512-pcDMpSckekV8xj2SSKO8PaqaJhrmDx84zUNip0kOWsT/ERhhDpnWkr6KXMqRXVp2y5CW9pp4LwOFdtpt3rhRgw==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.1.6.tgz", + "integrity": "sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw==", "cpu": [ "arm64" ], @@ -7300,9 +7744,9 @@ } }, "node_modules/@react-email/preview-server/node_modules/@next/swc-win32-x64-msvc": { - "version": "16.0.11", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.0.11.tgz", - "integrity": "sha512-Zzo9NLLRzBSHw9zOGpER/gdc5rofZHLjR2OIUIfoBaN2Oo5zWRl43IF5rMSX2LX7MPLTx4Ww8+5lNHAhXgitnA==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.1.6.tgz", + "integrity": "sha512-NRfO39AIrzBnixKbjuo2YiYhB6o9d8v/ymU9m/Xk8cyVk+k7XylniXkHwjs4s70wedVffc6bQNbufk5v0xEm0A==", "cpu": [ "x64" ], @@ -7326,15 +7770,58 @@ "tslib": "^2.8.0" } }, + "node_modules/@react-email/preview-server/node_modules/esbuild": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.10.tgz", + "integrity": "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.10", + "@esbuild/android-arm": "0.25.10", + "@esbuild/android-arm64": "0.25.10", + "@esbuild/android-x64": "0.25.10", + "@esbuild/darwin-arm64": "0.25.10", + "@esbuild/darwin-x64": "0.25.10", + "@esbuild/freebsd-arm64": "0.25.10", + "@esbuild/freebsd-x64": "0.25.10", + "@esbuild/linux-arm": "0.25.10", + "@esbuild/linux-arm64": "0.25.10", + "@esbuild/linux-ia32": "0.25.10", + "@esbuild/linux-loong64": "0.25.10", + "@esbuild/linux-mips64el": "0.25.10", + "@esbuild/linux-ppc64": "0.25.10", + "@esbuild/linux-riscv64": "0.25.10", + "@esbuild/linux-s390x": "0.25.10", + "@esbuild/linux-x64": "0.25.10", + "@esbuild/netbsd-arm64": "0.25.10", + "@esbuild/netbsd-x64": "0.25.10", + "@esbuild/openbsd-arm64": "0.25.10", + "@esbuild/openbsd-x64": "0.25.10", + "@esbuild/openharmony-arm64": "0.25.10", + "@esbuild/sunos-x64": "0.25.10", + "@esbuild/win32-arm64": "0.25.10", + "@esbuild/win32-ia32": "0.25.10", + "@esbuild/win32-x64": "0.25.10" + } + }, "node_modules/@react-email/preview-server/node_modules/next": { - "version": "16.0.11", - "resolved": "https://registry.npmjs.org/next/-/next-16.0.11.tgz", - "integrity": "sha512-Xlo2aFWaoypPzXr4PFLSNmxrzNptlp+hgxnG9Y2THYvHrvmXIuHUyNAWO6Q+F4rm4/bmTOukprXEyF/j4qsC2A==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/next/-/next-16.1.6.tgz", + "integrity": "sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw==", "dev": true, "license": "MIT", "dependencies": { - "@next/env": "16.0.11", + "@next/env": "16.1.6", "@swc/helpers": "0.5.15", + "baseline-browser-mapping": "^2.8.3", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" @@ -7346,14 +7833,14 @@ "node": ">=20.9.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "16.0.11", - "@next/swc-darwin-x64": "16.0.11", - "@next/swc-linux-arm64-gnu": "16.0.11", - "@next/swc-linux-arm64-musl": "16.0.11", - "@next/swc-linux-x64-gnu": "16.0.11", - "@next/swc-linux-x64-musl": "16.0.11", - "@next/swc-win32-arm64-msvc": "16.0.11", - "@next/swc-win32-x64-msvc": "16.0.11", + "@next/swc-darwin-arm64": "16.1.6", + "@next/swc-darwin-x64": "16.1.6", + "@next/swc-linux-arm64-gnu": "16.1.6", + "@next/swc-linux-arm64-musl": "16.1.6", + "@next/swc-linux-x64-gnu": "16.1.6", + "@next/swc-linux-x64-musl": "16.1.6", + "@next/swc-win32-arm64-msvc": "16.1.6", + "@next/swc-win32-x64-msvc": "16.1.6", "sharp": "^0.34.4" }, "peerDependencies": { @@ -7554,6 +8041,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, "license": "MIT" }, "node_modules/@scarf/scarf": { @@ -9397,6 +9885,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, "license": "MIT" }, "node_modules/@types/express": { @@ -9467,12 +9956,14 @@ "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, "license": "MIT" }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true, "license": "MIT" }, "node_modules/@types/jsonwebtoken": { @@ -9674,6 +10165,7 @@ "version": "8.54.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.54.0.tgz", "integrity": "sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ==", + "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", @@ -9702,6 +10194,7 @@ "version": "7.0.5", "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 4" @@ -9711,6 +10204,7 @@ "version": "8.54.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.54.0.tgz", "integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==", + "dev": true, "license": "MIT", "peer": true, "dependencies": { @@ -9736,6 +10230,7 @@ "version": "8.54.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.54.0.tgz", "integrity": "sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g==", + "dev": true, "license": "MIT", "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.54.0", @@ -9757,6 +10252,7 @@ "version": "8.54.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.54.0.tgz", "integrity": "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==", + "dev": true, "license": "MIT", "dependencies": { "@typescript-eslint/types": "8.54.0", @@ -9774,6 +10270,7 @@ "version": "8.54.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.54.0.tgz", "integrity": "sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==", + "dev": true, "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -9790,6 +10287,7 @@ "version": "8.54.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.54.0.tgz", "integrity": "sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA==", + "dev": true, "license": "MIT", "dependencies": { "@typescript-eslint/types": "8.54.0", @@ -9814,6 +10312,7 @@ "version": "8.54.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.54.0.tgz", "integrity": "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==", + "dev": true, "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -9827,6 +10326,7 @@ "version": "8.54.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.54.0.tgz", "integrity": "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==", + "dev": true, "license": "MIT", "dependencies": { "@typescript-eslint/project-service": "8.54.0", @@ -9854,6 +10354,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -9863,6 +10364,7 @@ "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -9878,6 +10380,7 @@ "version": "8.54.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.54.0.tgz", "integrity": "sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==", + "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", @@ -9901,6 +10404,7 @@ "version": "8.54.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.54.0.tgz", "integrity": "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==", + "dev": true, "license": "MIT", "dependencies": { "@typescript-eslint/types": "8.54.0", @@ -9921,6 +10425,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -9934,6 +10439,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -9947,6 +10453,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -9960,6 +10467,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -9973,6 +10481,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -9986,6 +10495,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -9999,6 +10509,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10012,6 +10523,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10025,6 +10537,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10038,6 +10551,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10051,6 +10565,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10064,6 +10579,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10077,6 +10593,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10090,6 +10607,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10103,6 +10621,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10116,6 +10635,7 @@ "cpu": [ "wasm32" ], + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -10132,6 +10652,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10145,6 +10666,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10158,6 +10680,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10192,6 +10715,7 @@ "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, "license": "MIT", "peer": true, "bin": { @@ -10205,6 +10729,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" @@ -10214,6 +10739,7 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -10284,6 +10810,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -10355,6 +10882,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, "license": "Apache-2.0", "engines": { "node": ">= 0.4" @@ -10364,6 +10892,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -10380,6 +10909,7 @@ "version": "3.1.9", "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -10412,6 +10942,7 @@ "version": "1.2.5", "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.7", @@ -10432,6 +10963,7 @@ "version": "1.2.6", "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -10453,6 +10985,7 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -10471,6 +11004,7 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -10489,6 +11023,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.7", @@ -10505,6 +11040,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, "license": "MIT", "dependencies": { "array-buffer-byte-length": "^1.0.1", @@ -10540,6 +11076,7 @@ "version": "0.0.8", "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", + "dev": true, "license": "MIT" }, "node_modules/async": { @@ -10552,6 +11089,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -10578,6 +11116,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, "license": "MIT", "dependencies": { "possible-typed-array-names": "^1.0.0" @@ -10593,6 +11132,7 @@ "version": "4.11.0", "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.0.tgz", "integrity": "sha512-ilYanEU8vxxBexpJd8cWM4ElSQq4QctCLKih0TSfjIfCQTeyH/6zVrmIJfLPrKTKJRbiG+cfnZbQIjAlJmF1jQ==", + "dev": true, "license": "MPL-2.0", "engines": { "node": ">=4" @@ -10613,6 +11153,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, "license": "Apache-2.0", "engines": { "node": ">= 0.4" @@ -10633,6 +11174,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, "license": "MIT" }, "node_modules/base64-js": { @@ -10669,6 +11211,7 @@ "version": "2.9.4", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.4.tgz", "integrity": "sha512-ZCQ9GEWl73BVm8bu5Fts8nt7MHdbt5vY9bP6WGnUh+r3l8M7CgfyTlwsgCbMC66BNxPr6Xoce3j66Ms5YUQTNA==", + "dev": true, "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.js" @@ -10769,6 +11312,7 @@ "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -10791,6 +11335,7 @@ "version": "4.28.1", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, "funding": [ { "type": "opencollective", @@ -10871,6 +11416,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.0", @@ -10918,6 +11464,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -10957,6 +11504,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -11158,6 +11706,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -11170,6 +11719,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, "license": "MIT" }, "node_modules/color-string": { @@ -11240,6 +11790,7 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, "license": "MIT" }, "node_modules/conf": { @@ -11333,6 +11884,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, "license": "MIT" }, "node_modules/cookie-parser": { @@ -11842,12 +12394,14 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true, "license": "BSD-2-Clause" }, "node_modules/data-view-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -11865,6 +12419,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -11882,6 +12437,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -11997,6 +12553,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, "license": "MIT" }, "node_modules/deepmerge": { @@ -12012,6 +12569,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, "license": "MIT", "dependencies": { "es-define-property": "^1.0.0", @@ -12029,6 +12587,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, "license": "MIT", "dependencies": { "define-data-property": "^1.0.1", @@ -12110,6 +12669,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "esutils": "^2.0.2" @@ -12904,12 +13464,14 @@ "version": "1.5.266", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.266.tgz", "integrity": "sha512-kgWEglXvkEfMH7rxP5OSZZwnaDWT7J9EoZCujhnpLbfi0bbNtRkgdX2E3gt0Uer11c61qCYktB3hwkAS325sJg==", + "dev": true, "license": "ISC" }, "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, "license": "MIT" }, "node_modules/enabled": { @@ -13116,6 +13678,7 @@ "version": "1.24.0", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", + "dev": true, "license": "MIT", "dependencies": { "array-buffer-byte-length": "^1.0.2", @@ -13202,6 +13765,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz", "integrity": "sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==", + "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -13256,6 +13820,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, "license": "MIT", "dependencies": { "hasown": "^2.0.2" @@ -13268,6 +13833,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, "license": "MIT", "dependencies": { "is-callable": "^1.2.7", @@ -13282,9 +13848,9 @@ } }, "node_modules/esbuild": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", - "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -13296,32 +13862,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.2", - "@esbuild/android-arm": "0.27.2", - "@esbuild/android-arm64": "0.27.2", - "@esbuild/android-x64": "0.27.2", - "@esbuild/darwin-arm64": "0.27.2", - "@esbuild/darwin-x64": "0.27.2", - "@esbuild/freebsd-arm64": "0.27.2", - "@esbuild/freebsd-x64": "0.27.2", - "@esbuild/linux-arm": "0.27.2", - "@esbuild/linux-arm64": "0.27.2", - "@esbuild/linux-ia32": "0.27.2", - "@esbuild/linux-loong64": "0.27.2", - "@esbuild/linux-mips64el": "0.27.2", - "@esbuild/linux-ppc64": "0.27.2", - "@esbuild/linux-riscv64": "0.27.2", - "@esbuild/linux-s390x": "0.27.2", - "@esbuild/linux-x64": "0.27.2", - "@esbuild/netbsd-arm64": "0.27.2", - "@esbuild/netbsd-x64": "0.27.2", - "@esbuild/openbsd-arm64": "0.27.2", - "@esbuild/openbsd-x64": "0.27.2", - "@esbuild/openharmony-arm64": "0.27.2", - "@esbuild/sunos-x64": "0.27.2", - "@esbuild/win32-arm64": "0.27.2", - "@esbuild/win32-ia32": "0.27.2", - "@esbuild/win32-x64": "0.27.2" + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" } }, "node_modules/esbuild-node-externals": { @@ -13372,6 +13938,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -13384,6 +13951,7 @@ "version": "9.39.2", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", + "dev": true, "license": "MIT", "peer": true, "dependencies": { @@ -13444,6 +14012,7 @@ "version": "16.1.6", "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.1.6.tgz", "integrity": "sha512-vKq40io2B0XtkkNDYyleATwblNt8xuh3FWp8SpSz3pt7P01OkBFlKsJZ2mWt5WsCySlDQLckb1zMY9yE9Qy0LA==", + "dev": true, "license": "MIT", "dependencies": { "@next/eslint-plugin-next": "16.1.6", @@ -13470,6 +14039,7 @@ "version": "16.4.0", "resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz", "integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -13482,6 +14052,7 @@ "version": "0.3.9", "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, "license": "MIT", "dependencies": { "debug": "^3.2.7", @@ -13493,6 +14064,7 @@ "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.1" @@ -13502,6 +14074,7 @@ "version": "3.10.1", "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.10.1.tgz", "integrity": "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==", + "dev": true, "license": "ISC", "dependencies": { "@nolyfill/is-core-module": "1.0.39", @@ -13536,6 +14109,7 @@ "version": "2.12.1", "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", + "dev": true, "license": "MIT", "dependencies": { "debug": "^3.2.7" @@ -13553,6 +14127,7 @@ "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.1" @@ -13562,6 +14137,7 @@ "version": "2.32.0", "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", + "dev": true, "license": "MIT", "peer": true, "dependencies": { @@ -13596,6 +14172,7 @@ "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.1" @@ -13605,6 +14182,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -13614,6 +14192,7 @@ "version": "6.10.2", "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", + "dev": true, "license": "MIT", "dependencies": { "aria-query": "^5.3.2", @@ -13643,6 +14222,7 @@ "version": "7.37.5", "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", + "dev": true, "license": "MIT", "dependencies": { "array-includes": "^3.1.8", @@ -13675,6 +14255,7 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz", "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.24.4", @@ -13694,6 +14275,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=18.0.0" @@ -13706,6 +14288,7 @@ "version": "2.0.0-next.5", "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dev": true, "license": "MIT", "dependencies": { "is-core-module": "^2.13.0", @@ -13723,6 +14306,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -13732,6 +14316,7 @@ "version": "8.4.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", @@ -13748,6 +14333,7 @@ "version": "4.2.1", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -13760,6 +14346,7 @@ "version": "10.4.0", "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "acorn": "^8.15.0", @@ -13777,6 +14364,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "estraverse": "^5.1.0" @@ -13789,6 +14377,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" @@ -13801,6 +14390,7 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=4.0" @@ -13810,6 +14400,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" @@ -13954,6 +14545,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, "license": "MIT" }, "node_modules/fast-equals": { @@ -13969,6 +14561,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -13985,6 +14578,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -13997,12 +14591,14 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, "license": "MIT" }, "node_modules/fast-sha256": { @@ -14050,6 +14646,7 @@ "version": "1.19.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, "license": "ISC", "dependencies": { "reusify": "^1.0.4" @@ -14059,6 +14656,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12.0.0" @@ -14082,6 +14680,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, "license": "MIT", "dependencies": { "flat-cache": "^4.0.0" @@ -14142,6 +14741,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, "license": "MIT", "dependencies": { "locate-path": "^6.0.0", @@ -14158,6 +14758,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, "license": "MIT", "dependencies": { "flatted": "^3.2.9", @@ -14171,6 +14772,7 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, "license": "ISC" }, "node_modules/fn.name": { @@ -14203,6 +14805,7 @@ "version": "0.3.5", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, "license": "MIT", "dependencies": { "is-callable": "^1.2.7" @@ -14340,6 +14943,7 @@ "version": "1.1.8", "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -14360,6 +14964,7 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -14369,6 +14974,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -14378,6 +14984,7 @@ "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -14467,6 +15074,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -14484,6 +15092,7 @@ "version": "4.13.0", "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", + "dev": true, "license": "MIT", "dependencies": { "resolve-pkg-maps": "^1.0.0" @@ -14519,6 +15128,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.3" @@ -14546,6 +15156,7 @@ "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -14555,6 +15166,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, "license": "MIT", "dependencies": { "define-properties": "^1.2.1", @@ -14611,6 +15223,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -14623,6 +15236,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -14632,6 +15246,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, "license": "MIT", "dependencies": { "es-define-property": "^1.0.0" @@ -14644,6 +15259,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.0" @@ -14716,12 +15332,14 @@ "version": "0.25.1", "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, "license": "MIT" }, "node_modules/hermes-parser": { "version": "0.25.1", "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, "license": "MIT", "dependencies": { "hermes-estree": "0.25.1" @@ -14843,6 +15461,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 4" @@ -14852,6 +15471,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, "license": "MIT", "dependencies": { "parent-module": "^1.0.0", @@ -14868,6 +15488,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.8.19" @@ -14899,6 +15520,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -14976,6 +15598,7 @@ "version": "3.0.5", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -14993,6 +15616,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, "license": "MIT", "dependencies": { "async-function": "^1.0.0", @@ -15012,6 +15636,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, "license": "MIT", "dependencies": { "has-bigints": "^1.0.2" @@ -15040,6 +15665,7 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -15056,6 +15682,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", + "dev": true, "license": "MIT", "dependencies": { "semver": "^7.7.1" @@ -15065,6 +15692,7 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -15077,6 +15705,7 @@ "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, "license": "MIT", "dependencies": { "hasown": "^2.0.2" @@ -15092,6 +15721,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -15109,6 +15739,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -15134,6 +15765,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3" @@ -15159,6 +15791,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.4", @@ -15190,6 +15823,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -15202,6 +15836,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -15223,6 +15858,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -15245,6 +15881,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -15263,6 +15900,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -15275,6 +15913,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3" @@ -15302,6 +15941,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -15318,6 +15958,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -15335,6 +15976,7 @@ "version": "1.1.15", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, "license": "MIT", "dependencies": { "which-typed-array": "^1.1.16" @@ -15350,6 +15992,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -15362,6 +16005,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3" @@ -15377,6 +16021,7 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -15393,6 +16038,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, "license": "MIT" }, "node_modules/isexe": { @@ -15409,6 +16055,7 @@ "version": "1.1.5", "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "dev": true, "license": "MIT", "dependencies": { "define-data-property": "^1.1.4", @@ -15442,7 +16089,7 @@ "version": "2.6.1", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", - "devOptional": true, + "dev": true, "license": "MIT", "bin": { "jiti": "lib/jiti-cli.mjs" @@ -15479,6 +16126,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, "license": "MIT", "bin": { "jsesc": "bin/jsesc" @@ -15491,12 +16139,14 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, "license": "MIT" }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, "license": "MIT" }, "node_modules/json-schema-typed": { @@ -15510,12 +16160,14 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, "license": "MIT" }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, "license": "MIT", "bin": { "json5": "lib/cli.js" @@ -15550,6 +16202,7 @@ "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, "license": "MIT", "dependencies": { "array-includes": "^3.1.6", @@ -15586,6 +16239,7 @@ "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, "license": "MIT", "dependencies": { "json-buffer": "3.0.1" @@ -15611,12 +16265,14 @@ "version": "0.3.23", "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", + "dev": true, "license": "CC0-1.0" }, "node_modules/language-tags": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "dev": true, "license": "MIT", "dependencies": { "language-subtag-registry": "^0.3.20" @@ -15638,6 +16294,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1", @@ -15945,6 +16602,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, "license": "MIT", "dependencies": { "p-locate": "^5.0.0" @@ -16014,6 +16672,7 @@ "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, "license": "MIT" }, "node_modules/lodash.once": { @@ -16055,6 +16714,7 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, "license": "ISC", "dependencies": { "yallist": "^3.0.2" @@ -16212,6 +16872,7 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -16315,6 +16976,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -16436,6 +17098,7 @@ "version": "0.3.4", "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "dev": true, "license": "MIT", "bin": { "napi-postinstall": "lib/cli.js" @@ -16451,6 +17114,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, "license": "MIT" }, "node_modules/negotiator": { @@ -16652,6 +17316,7 @@ "version": "2.0.27", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, "license": "MIT" }, "node_modules/nodemailer": { @@ -16746,6 +17411,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -16765,6 +17431,7 @@ "version": "4.1.7", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -16785,6 +17452,7 @@ "version": "1.1.9", "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -16800,6 +17468,7 @@ "version": "2.0.8", "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.7", @@ -16818,6 +17487,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.7", @@ -16832,6 +17502,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -16905,6 +17576,7 @@ "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, "license": "MIT", "dependencies": { "deep-is": "^0.1.3", @@ -17215,6 +17887,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, "license": "MIT", "dependencies": { "get-intrinsic": "^1.2.6", @@ -17232,6 +17905,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" @@ -17247,6 +17921,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, "license": "MIT", "dependencies": { "p-limit": "^3.0.2" @@ -17269,6 +17944,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, "license": "MIT", "dependencies": { "callsites": "^3.0.0" @@ -17303,6 +17979,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -17321,6 +17998,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, "license": "MIT" }, "node_modules/path-scurry": { @@ -17484,6 +18162,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -17527,6 +18206,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -17601,12 +18281,12 @@ } }, "node_modules/posthog-node": { - "version": "5.24.10", - "resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-5.24.10.tgz", - "integrity": "sha512-C4ueZUrifTJMDFngybSWQ+GthcqCqPiCcGg5qnjoh+f6ie3+tdhFROqqshjttpQ6Q4DPM40USPTmU/UBYqgsbA==", + "version": "5.24.11", + "resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-5.24.11.tgz", + "integrity": "sha512-tDXbYyXJyh0oUEo1SumCzmXY0FZNB0avAq0uXMo6o6JinzwY8u5cygqAgUyMDIGG8u0p6tBHq++foqULXaPmiA==", "license": "MIT", "dependencies": { - "@posthog/core": "1.20.0" + "@posthog/core": "1.20.1" }, "engines": { "node": "^20.20.0 || >=22.22.0" @@ -17642,6 +18322,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.8.0" @@ -17729,6 +18410,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -17799,6 +18481,7 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, "funding": [ { "type": "github", @@ -17890,9 +18573,9 @@ } }, "node_modules/react-day-picker": { - "version": "9.13.0", - "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-9.13.0.tgz", - "integrity": "sha512-euzj5Hlq+lOHqI53NiuNhCP8HWgsPf/bBAVijR50hNaY1XwjKjShAnIe8jm8RD2W9IJUvihDIZ+KrmqfFzNhFQ==", + "version": "9.13.1", + "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-9.13.1.tgz", + "integrity": "sha512-9nx2lBBJ0VZw5jJekId3DishwnJLiqY1Me1JvCrIyqbWwcflBTVaEkiK+w1bre5oMNWYo722eu+8UAMXWMqktw==", "license": "MIT", "dependencies": { "@date-fns/tz": "^1.4.1", @@ -17940,9 +18623,9 @@ } }, "node_modules/react-email": { - "version": "5.2.7", - "resolved": "https://registry.npmjs.org/react-email/-/react-email-5.2.7.tgz", - "integrity": "sha512-qTRqN+Ftvc6k8COSDM3IROmimbBWMykWFWxk4FKDKjoLw9bMfwavXJSeACmkwoiv+7jws/Pm1xNf9eoR7blt8g==", + "version": "5.2.8", + "resolved": "https://registry.npmjs.org/react-email/-/react-email-5.2.8.tgz", + "integrity": "sha512-noPcnpl78vsyBnhiKCzxK9Mdsv7ncAYI80osS5kbMgaKH2IgPtPab5BzLJX6INXuiNk5ju+9YRnCjPoPTOHZjA==", "dev": true, "license": "MIT", "dependencies": { @@ -18956,6 +19639,7 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -18978,6 +19662,7 @@ "version": "1.5.4", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -19035,6 +19720,7 @@ "version": "1.22.11", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, "license": "MIT", "dependencies": { "is-core-module": "^2.16.1", @@ -19055,6 +19741,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -19064,6 +19751,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" @@ -19073,6 +19761,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, "license": "MIT", "engines": { "iojs": ">=1.0.0", @@ -19105,6 +19794,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, "funding": [ { "type": "github", @@ -19134,6 +19824,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -19173,6 +19864,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -19189,6 +19881,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -19288,6 +19981,7 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, "license": "MIT", "dependencies": { "define-data-property": "^1.1.4", @@ -19305,6 +19999,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, "license": "MIT", "dependencies": { "define-data-property": "^1.1.4", @@ -19320,6 +20015,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -19751,6 +20447,7 @@ "version": "0.0.5", "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", "integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==", + "dev": true, "license": "MIT" }, "node_modules/stack-trace": { @@ -19810,6 +20507,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -19896,6 +20594,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", + "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.7", @@ -19910,6 +20609,7 @@ "version": "4.0.12", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -19937,6 +20637,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, "license": "MIT", "dependencies": { "define-properties": "^1.1.3", @@ -19947,6 +20648,7 @@ "version": "1.2.10", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -19968,6 +20670,7 @@ "version": "1.0.9", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -19986,6 +20689,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.7", @@ -20042,6 +20746,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -20061,6 +20766,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -20142,6 +20848,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -20154,6 +20861,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -20322,6 +21030,7 @@ "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", @@ -20397,6 +21106,7 @@ "version": "2.4.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "dev": true, "license": "MIT", "engines": { "node": ">=18.12" @@ -20505,6 +21215,7 @@ "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, "license": "MIT", "dependencies": { "@types/json5": "^0.0.29", @@ -20517,6 +21228,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, "license": "MIT", "dependencies": { "minimist": "^1.2.0" @@ -20594,6 +21306,7 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1" @@ -20636,6 +21349,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -20650,6 +21364,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -20669,6 +21384,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", @@ -20690,6 +21406,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.7", @@ -20710,6 +21427,7 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, "license": "Apache-2.0", "peer": true, "bin": { @@ -20724,6 +21442,7 @@ "version": "8.54.0", "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.54.0.tgz", "integrity": "sha512-CKsJ+g53QpsNPqbzUsfKVgd3Lny4yKZ1pP4qN3jdMOg/sisIDLGyDMezycquXLE5JsEU0wp3dGNdzig0/fmSVQ==", + "dev": true, "license": "MIT", "dependencies": { "@typescript-eslint/eslint-plugin": "8.54.0", @@ -20766,6 +21485,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -20800,6 +21520,7 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", + "dev": true, "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -20834,6 +21555,7 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.2.tgz", "integrity": "sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA==", + "dev": true, "funding": [ { "type": "opencollective", @@ -20864,6 +21586,7 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" @@ -21038,6 +21761,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, "license": "MIT", "dependencies": { "is-bigint": "^1.1.0", @@ -21057,6 +21781,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -21084,6 +21809,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, "license": "MIT", "dependencies": { "is-map": "^2.0.3", @@ -21102,6 +21828,7 @@ "version": "1.1.19", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "dev": true, "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", @@ -21178,6 +21905,7 @@ "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -21327,6 +22055,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, "license": "ISC" }, "node_modules/yaml": { @@ -21397,6 +22126,7 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=10" diff --git a/package.json b/package.json index 6b9606b7..4ac22477 100644 --- a/package.json +++ b/package.json @@ -34,8 +34,8 @@ }, "dependencies": { "@asteasolutions/zod-to-openapi": "8.4.0", - "@aws-sdk/client-s3": "3.983.0", - "@faker-js/faker": "10.2.0", + "@aws-sdk/client-s3": "3.985.0", + "@faker-js/faker": "10.3.0", "@headlessui/react": "2.2.9", "@hookform/resolvers": "5.2.2", "@monaco-editor/react": "4.7.0", @@ -81,8 +81,6 @@ "d3": "7.9.0", "date-fns": "4.1.0", "drizzle-orm": "0.45.1", - "eslint": "9.39.2", - "eslint-config-next": "16.1.6", "express": "5.2.1", "express-rate-limit": "8.2.1", "glob": "13.0.1", @@ -104,10 +102,10 @@ "nodemailer": "8.0.0", "oslo": "1.2.1", "pg": "8.18.0", - "posthog-node": "5.24.10", + "posthog-node": "5.24.11", "qrcode.react": "4.2.0", "react": "19.2.4", - "react-day-picker": "9.13.0", + "react-day-picker": "9.13.1", "react-dom": "19.2.4", "react-easy-sort": "1.8.0", "react-hook-form": "7.71.1", @@ -135,7 +133,7 @@ "devDependencies": { "@dotenvx/dotenvx": "1.52.0", "@esbuild-plugins/tsconfig-paths": "0.1.2", - "@react-email/preview-server": "5.2.7", + "@react-email/preview-server": "5.2.8", "@tailwindcss/postcss": "4.1.18", "@tanstack/react-query-devtools": "5.91.3", "@types/better-sqlite3": "7.6.13", @@ -161,11 +159,13 @@ "@types/yargs": "17.0.35", "babel-plugin-react-compiler": "1.0.0", "drizzle-kit": "0.31.8", - "esbuild": "0.27.2", + "esbuild": "0.27.3", "esbuild-node-externals": "1.20.1", + "eslint": "9.39.2", + "eslint-config-next": "16.1.6", "postcss": "8.5.6", "prettier": "3.8.1", - "react-email": "5.2.7", + "react-email": "5.2.8", "tailwindcss": "4.1.18", "tsc-alias": "1.8.16", "tsx": "4.21.0", From 57b8c69983cf8c00e11bc049232c9a6c152fbea7 Mon Sep 17 00:00:00 2001 From: Lokowitz Date: Sat, 7 Feb 2026 08:34:56 +0000 Subject: [PATCH 36/81] remove date-fns --- package-lock.json | 1 - package.json | 1 - src/app/[orgId]/settings/sites/create/page.tsx | 1 - 3 files changed, 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index cb91cb3e..12309b50 100644 --- a/package-lock.json +++ b/package-lock.json @@ -55,7 +55,6 @@ "cors": "2.8.6", "crypto-js": "4.2.0", "d3": "7.9.0", - "date-fns": "4.1.0", "drizzle-orm": "0.45.1", "express": "5.2.1", "express-rate-limit": "8.2.1", diff --git a/package.json b/package.json index 4ac22477..ee1b6792 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,6 @@ "cors": "2.8.6", "crypto-js": "4.2.0", "d3": "7.9.0", - "date-fns": "4.1.0", "drizzle-orm": "0.45.1", "express": "5.2.1", "express-rate-limit": "8.2.1", diff --git a/src/app/[orgId]/settings/sites/create/page.tsx b/src/app/[orgId]/settings/sites/create/page.tsx index 194a2822..cb6365b7 100644 --- a/src/app/[orgId]/settings/sites/create/page.tsx +++ b/src/app/[orgId]/settings/sites/create/page.tsx @@ -63,7 +63,6 @@ import { QRCodeCanvas } from "qrcode.react"; import { useTranslations } from "next-intl"; import { build } from "@server/build"; import { NewtSiteInstallCommands } from "@app/components/newt-install-commands"; -import { id } from "date-fns/locale"; type SiteType = "newt" | "wireguard" | "local"; From 63f7dd1d203f3661d8299d2e4b2c33796e9adb70 Mon Sep 17 00:00:00 2001 From: Lokowitz Date: Sat, 7 Feb 2026 08:48:58 +0000 Subject: [PATCH 37/81] fix lint error --- .../routers/generatedLicense/generateNewEnterpriseLicense.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/private/routers/generatedLicense/generateNewEnterpriseLicense.ts b/server/private/routers/generatedLicense/generateNewEnterpriseLicense.ts index 7cffb9d7..ee81ef1a 100644 --- a/server/private/routers/generatedLicense/generateNewEnterpriseLicense.ts +++ b/server/private/routers/generatedLicense/generateNewEnterpriseLicense.ts @@ -113,7 +113,7 @@ export async function generateNewEnterpriseLicense( } const tier = licenseData.tier === "big_license" ? LicenseId.BIG_LICENSE : LicenseId.SMALL_LICENSE; - const tierPrice = getLicensePriceSet()[tier] + const tierPrice = getLicensePriceSet()[tier]; const session = await stripe!.checkout.sessions.create({ client_reference_id: keyId.toString(), From 44f208188263a07c07b3a78b0de55c3b84e809d1 Mon Sep 17 00:00:00 2001 From: Lokowitz Date: Mon, 9 Feb 2026 17:08:59 +0000 Subject: [PATCH 38/81] update packages --- package-lock.json | 148 +++++++++++++++++++++------------------------- package.json | 12 ++-- 2 files changed, 72 insertions(+), 88 deletions(-) diff --git a/package-lock.json b/package-lock.json index 12309b50..be7656e7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -45,7 +45,7 @@ "@tanstack/react-query": "5.90.20", "@tanstack/react-table": "8.21.3", "arctic": "3.7.0", - "axios": "1.13.4", + "axios": "1.13.5", "better-sqlite3": "11.9.1", "canvas-confetti": "1.9.4", "class-variance-authority": "0.7.1", @@ -69,15 +69,15 @@ "lucide-react": "0.563.0", "maxmind": "5.0.5", "moment": "2.30.1", - "next": "15.5.11", + "next": "15.5.12", "next-intl": "4.8.2", "next-themes": "0.4.6", "nextjs-toploader": "3.9.17", "node-cache": "5.1.2", - "nodemailer": "8.0.0", + "nodemailer": "8.0.1", "oslo": "1.2.1", "pg": "8.18.0", - "posthog-node": "5.24.11", + "posthog-node": "5.24.13", "qrcode.react": "4.2.0", "react": "19.2.4", "react-day-picker": "9.13.1", @@ -121,7 +121,7 @@ "@types/jmespath": "0.15.2", "@types/js-yaml": "4.0.9", "@types/jsonwebtoken": "9.0.10", - "@types/node": "25.2.1", + "@types/node": "25.2.2", "@types/nodemailer": "7.0.9", "@types/nprogress": "0.2.3", "@types/pg": "8.16.0", @@ -133,7 +133,7 @@ "@types/ws": "8.18.1", "@types/yargs": "17.0.35", "babel-plugin-react-compiler": "1.0.0", - "drizzle-kit": "0.31.8", + "drizzle-kit": "0.31.9", "esbuild": "0.27.3", "esbuild-node-externals": "1.20.1", "eslint": "9.39.2", @@ -3418,10 +3418,9 @@ } }, "node_modules/@next/env": { - "version": "15.5.11", - "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.11.tgz", - "integrity": "sha512-g9s5SS9gC7GJCEOR3OV3zqs7C5VddqxP9X+/6BpMbdXRkqsWfFf2CJPBZNvNEtAkKTNuRgRXAgNxSAXzfLdaTg==", - "license": "MIT" + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.12.tgz", + "integrity": "sha512-pUvdJN1on574wQHjaBfNGDt9Mz5utDSZFsIIQkMzPgNS8ZvT4H2mwOrOIClwsQOb6EGx5M76/CZr6G8i6pSpLg==" }, "node_modules/@next/eslint-plugin-next": { "version": "16.1.6", @@ -3434,13 +3433,12 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "15.5.7", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.7.tgz", - "integrity": "sha512-IZwtxCEpI91HVU/rAUOOobWSZv4P2DeTtNaCdHqLcTJU4wdNXgAySvKa/qJCgR5m6KI8UsKDXtO2B31jcaw1Yw==", + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.12.tgz", + "integrity": "sha512-RnRjBtH8S8eXCpUNkQ+543DUc7ys8y15VxmFU9HRqlo9BG3CcBUiwNtF8SNoi2xvGCVJq1vl2yYq+3oISBS0Zg==", "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "darwin" @@ -3450,13 +3448,12 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "15.5.7", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.7.tgz", - "integrity": "sha512-UP6CaDBcqaCBuiq/gfCEJw7sPEoX1aIjZHnBWN9v9qYHQdMKvCKcAVs4OX1vIjeE+tC5EIuwDTVIoXpUes29lg==", + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.12.tgz", + "integrity": "sha512-nqa9/7iQlboF1EFtNhWxQA0rQstmYRSBGxSM6g3GxvxHxcoeqVXfGNr9stJOme674m2V7r4E3+jEhhGvSQhJRA==", "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "darwin" @@ -3466,13 +3463,12 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "15.5.7", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.7.tgz", - "integrity": "sha512-NCslw3GrNIw7OgmRBxHtdWFQYhexoUCq+0oS2ccjyYLtcn1SzGzeM54jpTFonIMUjNbHmpKpziXnpxhSWLcmBA==", + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.12.tgz", + "integrity": "sha512-dCzAjqhDHwmoB2M4eYfVKqXs99QdQxNQVpftvP1eGVppamXh/OkDAwV737Zr0KPXEqRUMN4uCjh6mjO+XtF3Mw==", "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -3482,13 +3478,12 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "15.5.7", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.7.tgz", - "integrity": "sha512-nfymt+SE5cvtTrG9u1wdoxBr9bVB7mtKTcj0ltRn6gkP/2Nu1zM5ei8rwP9qKQP0Y//umK+TtkKgNtfboBxRrw==", + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.12.tgz", + "integrity": "sha512-+fpGWvQiITgf7PUtbWY1H7qUSnBZsPPLyyq03QuAKpVoTy/QUx1JptEDTQMVvQhvizCEuNLEeghrQUyXQOekuw==", "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -3498,13 +3493,12 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "15.5.7", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.7.tgz", - "integrity": "sha512-hvXcZvCaaEbCZcVzcY7E1uXN9xWZfFvkNHwbe/n4OkRhFWrs1J1QV+4U1BN06tXLdaS4DazEGXwgqnu/VMcmqw==", + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.12.tgz", + "integrity": "sha512-jSLvgdRRL/hrFAPqEjJf1fFguC719kmcptjNVDJl26BnJIpjL3KH5h6mzR4mAweociLQaqvt4UyzfbFjgAdDcw==", "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -3514,13 +3508,12 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "15.5.7", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.7.tgz", - "integrity": "sha512-4IUO539b8FmF0odY6/SqANJdgwn1xs1GkPO5doZugwZ3ETF6JUdckk7RGmsfSf7ws8Qb2YB5It33mvNL/0acqA==", + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.12.tgz", + "integrity": "sha512-/uaF0WfmYqQgLfPmN6BvULwxY0dufI2mlN2JbOKqqceZh1G4hjREyi7pg03zjfyS6eqNemHAZPSoP84x17vo6w==", "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -3530,13 +3523,12 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "15.5.7", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.7.tgz", - "integrity": "sha512-CpJVTkYI3ZajQkC5vajM7/ApKJUOlm6uP4BknM3XKvJ7VXAvCqSjSLmM0LKdYzn6nBJVSjdclx8nYJSa3xlTgQ==", + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.12.tgz", + "integrity": "sha512-xhsL1OvQSfGmlL5RbOmU+FV120urrgFpYLq+6U8C6KIym32gZT6XF/SDE92jKzzlPWskkbjOKCpqk5m4i8PEfg==", "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "win32" @@ -3546,13 +3538,12 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "15.5.7", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.7.tgz", - "integrity": "sha512-gMzgBX164I6DN+9/PGA+9dQiwmTkE4TloBNx8Kv9UiGARsr9Nba7IpcBRA1iTV9vwlYnrE3Uy6I7Aj6qLjQuqw==", + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.12.tgz", + "integrity": "sha512-Z1Dh6lhFkxvBDH1FoW6OU/L6prYwPSlwjLiZkExIAh8fbP6iI/M7iGTQAJPYJ9YFlWobCZ1PHbchFhFYb2ADkw==", "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "win32" @@ -4700,10 +4691,9 @@ } }, "node_modules/@posthog/core": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/@posthog/core/-/core-1.20.1.tgz", - "integrity": "sha512-uoTmWkYCtLYFpiK37/JCq+BuCA/OZn1qQZn5cPv1EEKt3ni3Zgg48xWCnSEyGFl5KKSXlfCruiRTwnbAtCgrBA==", - "license": "MIT", + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@posthog/core/-/core-1.20.2.tgz", + "integrity": "sha512-aQhrUzOHYr0z/bkwDsJ5hXahdh6oyeWdx2/CHwR6vFG3eK07J69lbuGOj+HGOOxJP1eAdNnsk8J0fj1vqRA9+A==", "dependencies": { "cross-spawn": "^7.0.6" } @@ -9984,11 +9974,10 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.2.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.1.tgz", - "integrity": "sha512-CPrnr8voK8vC6eEtyRzvMpgp3VyVRhgclonE7qYi6P9sXwYb59ucfrnmFBTaP0yUi8Gk4yZg/LlTJULGxvTNsg==", + "version": "25.2.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.2.tgz", + "integrity": "sha512-BkmoP5/FhRYek5izySdkOneRyXYN35I860MFAGupTdebyE66uZaR+bXLHq8k4DirE5DwQi3NuhvRU1jqTVwUrQ==", "devOptional": true, - "license": "MIT", "peer": true, "dependencies": { "undici-types": "~7.16.0" @@ -11138,13 +11127,12 @@ } }, "node_modules/axios": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.4.tgz", - "integrity": "sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg==", - "license": "MIT", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz", + "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, @@ -12781,11 +12769,10 @@ } }, "node_modules/drizzle-kit": { - "version": "0.31.8", - "resolved": "https://registry.npmjs.org/drizzle-kit/-/drizzle-kit-0.31.8.tgz", - "integrity": "sha512-O9EC/miwdnRDY10qRxM8P3Pg8hXe3LyU4ZipReKOgTwn4OqANmftj8XJz1UPUAS6NMHf0E2htjsbQujUTkncCg==", + "version": "0.31.9", + "resolved": "https://registry.npmjs.org/drizzle-kit/-/drizzle-kit-0.31.9.tgz", + "integrity": "sha512-GViD3IgsXn7trFyBUUHyTFBpH/FsHTxYJ66qdbVggxef4UBPHRYxQaRzYLTuekYnk9i5FIEL9pbBIwMqX/Uwrg==", "dev": true, - "license": "MIT", "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", @@ -17126,13 +17113,12 @@ } }, "node_modules/next": { - "version": "15.5.11", - "resolved": "https://registry.npmjs.org/next/-/next-15.5.11.tgz", - "integrity": "sha512-L2KPiKmqTDpRdeVDdPjhf43g2/VPe0NCNndq7OKDCgOLWtxe1kbr/zXGIZtYY7kZEAjRf7Bj/mwUFSr+tYC2Yg==", - "license": "MIT", + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/next/-/next-15.5.12.tgz", + "integrity": "sha512-Fi/wQ4Etlrn60rz78bebG1i1SR20QxvV8tVp6iJspjLUSHcZoeUXCt+vmWoEcza85ElZzExK/jJ/F6SvtGktjA==", "peer": true, "dependencies": { - "@next/env": "15.5.11", + "@next/env": "15.5.12", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", @@ -17145,14 +17131,14 @@ "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "15.5.7", - "@next/swc-darwin-x64": "15.5.7", - "@next/swc-linux-arm64-gnu": "15.5.7", - "@next/swc-linux-arm64-musl": "15.5.7", - "@next/swc-linux-x64-gnu": "15.5.7", - "@next/swc-linux-x64-musl": "15.5.7", - "@next/swc-win32-arm64-msvc": "15.5.7", - "@next/swc-win32-x64-msvc": "15.5.7", + "@next/swc-darwin-arm64": "15.5.12", + "@next/swc-darwin-x64": "15.5.12", + "@next/swc-linux-arm64-gnu": "15.5.12", + "@next/swc-linux-arm64-musl": "15.5.12", + "@next/swc-linux-x64-gnu": "15.5.12", + "@next/swc-linux-x64-musl": "15.5.12", + "@next/swc-win32-arm64-msvc": "15.5.12", + "@next/swc-win32-x64-msvc": "15.5.12", "sharp": "^0.34.3" }, "peerDependencies": { @@ -17319,10 +17305,9 @@ "license": "MIT" }, "node_modules/nodemailer": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-8.0.0.tgz", - "integrity": "sha512-xvVJf/f0bzmNpnRIbhCp/IKxaHgJ6QynvUbLXzzMRPG3LDQr5oXkYuw4uDFyFYs8cge8agwwrJAXZsd4hhMquw==", - "license": "MIT-0", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-8.0.1.tgz", + "integrity": "sha512-5kcldIXmaEjZcHR6F28IKGSgpmZHaF1IXLWFTG+Xh3S+Cce4MiakLtWY+PlBU69fLbRa8HlaGIrC/QolUpHkhg==", "engines": { "node": ">=6.0.0" } @@ -18280,12 +18265,11 @@ } }, "node_modules/posthog-node": { - "version": "5.24.11", - "resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-5.24.11.tgz", - "integrity": "sha512-tDXbYyXJyh0oUEo1SumCzmXY0FZNB0avAq0uXMo6o6JinzwY8u5cygqAgUyMDIGG8u0p6tBHq++foqULXaPmiA==", - "license": "MIT", + "version": "5.24.13", + "resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-5.24.13.tgz", + "integrity": "sha512-JHV2wUG5pkj50aV7XrHOWB7MZjqOsn+SKmbi1Q/Zy/94xvpmRt6mYDrF9Wp0HZTTUczyuqrXqK2eebgKJqlSzg==", "dependencies": { - "@posthog/core": "1.20.1" + "@posthog/core": "1.20.2" }, "engines": { "node": "^20.20.0 || >=22.22.0" diff --git a/package.json b/package.json index ee1b6792..b999bb20 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "@tanstack/react-query": "5.90.20", "@tanstack/react-table": "8.21.3", "arctic": "3.7.0", - "axios": "1.13.4", + "axios": "1.13.5", "better-sqlite3": "11.9.1", "canvas-confetti": "1.9.4", "class-variance-authority": "0.7.1", @@ -93,15 +93,15 @@ "lucide-react": "0.563.0", "maxmind": "5.0.5", "moment": "2.30.1", - "next": "15.5.11", + "next": "15.5.12", "next-intl": "4.8.2", "next-themes": "0.4.6", "nextjs-toploader": "3.9.17", "node-cache": "5.1.2", - "nodemailer": "8.0.0", + "nodemailer": "8.0.1", "oslo": "1.2.1", "pg": "8.18.0", - "posthog-node": "5.24.11", + "posthog-node": "5.24.13", "qrcode.react": "4.2.0", "react": "19.2.4", "react-day-picker": "9.13.1", @@ -145,7 +145,7 @@ "@types/jmespath": "0.15.2", "@types/js-yaml": "4.0.9", "@types/jsonwebtoken": "9.0.10", - "@types/node": "25.2.1", + "@types/node": "25.2.2", "@types/nodemailer": "7.0.9", "@types/nprogress": "0.2.3", "@types/pg": "8.16.0", @@ -157,7 +157,7 @@ "@types/ws": "8.18.1", "@types/yargs": "17.0.35", "babel-plugin-react-compiler": "1.0.0", - "drizzle-kit": "0.31.8", + "drizzle-kit": "0.31.9", "esbuild": "0.27.3", "esbuild-node-externals": "1.20.1", "eslint": "9.39.2", From 8429197b079c2dc8291420b3b2551643dec97e79 Mon Sep 17 00:00:00 2001 From: Lokowitz Date: Mon, 9 Feb 2026 19:56:32 +0000 Subject: [PATCH 39/81] fix .gitignore --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index df9179a4..004f95c1 100644 --- a/.gitignore +++ b/.gitignore @@ -51,4 +51,6 @@ dynamic/ scratch/ tsconfig.json hydrateSaas.ts -CLAUDE.md \ No newline at end of file +CLAUDE.md +drizzle.config.ts +server/setup/migrations.ts From b0af0d9cd5db40e41c882cba0c8548ed2f3bcf1d Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Tue, 10 Feb 2026 00:31:21 +0100 Subject: [PATCH 40/81] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20keep=20previous=20da?= =?UTF-8?q?ta?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/TanstackQueryProvider.tsx | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/components/TanstackQueryProvider.tsx b/src/components/TanstackQueryProvider.tsx index 9a6e7dd9..ab469c2b 100644 --- a/src/components/TanstackQueryProvider.tsx +++ b/src/components/TanstackQueryProvider.tsx @@ -1,11 +1,13 @@ "use client"; -import * as React from "react"; -import { QueryClientProvider } from "@tanstack/react-query"; -import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; -import { QueryClient } from "@tanstack/react-query"; import { useEnvContext } from "@app/hooks/useEnvContext"; import { createApiClient } from "@app/lib/api"; -import { durationToMs } from "@app/lib/durationToMs"; +import { + keepPreviousData, + QueryClient, + QueryClientProvider +} from "@tanstack/react-query"; +import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; +import * as React from "react"; export type ReactQueryProviderProps = { children: React.ReactNode; @@ -22,7 +24,8 @@ export function TanstackQueryProvider({ children }: ReactQueryProviderProps) { staleTime: 0, meta: { api - } + }, + placeholderData: keepPreviousData }, mutations: { meta: { api } From 7f73cde7945ed5cbcaba037e05fe7908cae2862b Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Tue, 10 Feb 2026 00:45:20 +0100 Subject: [PATCH 41/81] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refetch=20approval?= =?UTF-8?q?=20count=20every=2030s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/queries.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/lib/queries.ts b/src/lib/queries.ts index 6a3dd7ff..1d19c1e7 100644 --- a/src/lib/queries.ts +++ b/src/lib/queries.ts @@ -383,6 +383,12 @@ export const approvalQueries = { signal }); return res.data.data.count; + }, + refetchInterval: (query) => { + if (query.state.data) { + return durationToMs(30, "seconds"); + } + return false; } }) }; From da514ef3143cd983cff9d21db95c1351ac777af0 Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Tue, 10 Feb 2026 00:45:34 +0100 Subject: [PATCH 42/81] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/private/routers/approvals/countApprovals.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/private/routers/approvals/countApprovals.ts b/server/private/routers/approvals/countApprovals.ts index c68e422a..0885c7e8 100644 --- a/server/private/routers/approvals/countApprovals.ts +++ b/server/private/routers/approvals/countApprovals.ts @@ -19,7 +19,7 @@ import { fromError } from "zod-validation-error"; import type { Request, Response, NextFunction } from "express"; import { approvals, db, type Approval } from "@server/db"; -import { eq, sql, and } from "drizzle-orm"; +import { eq, sql, and, inArray } from "drizzle-orm"; import response from "@server/lib/response"; const paramsSchema = z.strictObject({ @@ -88,7 +88,7 @@ export async function countApprovals( .where( and( eq(approvals.orgId, orgId), - sql`${approvals.decision} in ${state}` + inArray(approvals.decision, state) ) ); From 3ba2cb19a9eda825dd44629f1b4bac49195454bf Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Tue, 10 Feb 2026 03:20:49 +0100 Subject: [PATCH 43/81] =?UTF-8?q?=E2=9C=A8=20approval=20feed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- messages/en-US.json | 2 + .../routers/approvals/listApprovals.ts | 161 ++++++++++++------ server/routers/client/listUserDevices.ts | 2 - src/components/ApprovalFeed.tsx | 80 ++++++--- src/lib/queries.ts | 43 ++++- 5 files changed, 201 insertions(+), 87 deletions(-) diff --git a/messages/en-US.json b/messages/en-US.json index f9627fcc..ac6ab691 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -459,6 +459,8 @@ "filterByApprovalState": "Filter By Approval State", "approvalListEmpty": "No approvals", "approvalState": "Approval State", + "approvalLoadMore": "Load more", + "loadingApprovals": "Loading Approvals", "approve": "Approve", "approved": "Approved", "denied": "Denied", diff --git a/server/private/routers/approvals/listApprovals.ts b/server/private/routers/approvals/listApprovals.ts index 600eec87..5639b440 100644 --- a/server/private/routers/approvals/listApprovals.ts +++ b/server/private/routers/approvals/listApprovals.ts @@ -30,7 +30,7 @@ import { currentFingerprint, type Approval } from "@server/db"; -import { eq, isNull, sql, not, and, desc } from "drizzle-orm"; +import { eq, isNull, sql, not, and, desc, gte, lte } from "drizzle-orm"; import response from "@server/lib/response"; import { getUserDeviceName } from "@server/db/names"; @@ -39,18 +39,26 @@ const paramsSchema = z.strictObject({ }); const querySchema = z.strictObject({ - limit: z - .string() + limit: z.coerce + .number() // for prettier formatting + .int() + .positive() .optional() - .default("1000") - .transform(Number) - .pipe(z.int().nonnegative()), - offset: z - .string() + .catch(20) + .default(20), + cursorPending: z.coerce // pending cursor + .number() + .int() + .max(1) // 0 means non pending + .min(0) // 1 means pending .optional() - .default("0") - .transform(Number) - .pipe(z.int().nonnegative()), + .catch(undefined), + cursorTimestamp: z.coerce + .number() + .int() + .positive() + .optional() + .catch(undefined), approvalState: z .enum(["pending", "approved", "denied", "all"]) .optional() @@ -63,13 +71,21 @@ const querySchema = z.strictObject({ .pipe(z.number().int().positive().optional()) }); -async function queryApprovals( - orgId: string, - limit: number, - offset: number, - approvalState: z.infer["approvalState"], - clientId?: number -) { +async function queryApprovals({ + orgId, + limit, + approvalState, + cursorPending, + cursorTimestamp, + clientId +}: { + orgId: string; + limit: number; + approvalState: z.infer["approvalState"]; + cursorPending?: number; + cursorTimestamp?: number; + clientId?: number; +}) { let state: Array = []; switch (approvalState) { case "pending": @@ -85,6 +101,26 @@ async function queryApprovals( state = ["approved", "denied", "pending"]; } + const conditions = [ + eq(approvals.orgId, orgId), + sql`${approvals.decision} in ${state}` + ]; + + if (clientId) { + conditions.push(eq(approvals.clientId, clientId)); + } + + const pendingSortKey = sql`CASE ${approvals.decision} WHEN 'pending' THEN 1 ELSE 0 END`; + + if (cursorPending != null && cursorTimestamp != null) { + // https://stackoverflow.com/a/79720298/10322846 + // composite cursor, next data means (pending, timestamp) <= cursor + conditions.push( + lte(pendingSortKey, cursorPending), + lte(approvals.timestamp, cursorTimestamp) + ); + } + const res = await db .select({ approvalId: approvals.approvalId, @@ -107,7 +143,8 @@ async function queryApprovals( fingerprintArch: currentFingerprint.arch, fingerprintSerialNumber: currentFingerprint.serialNumber, fingerprintUsername: currentFingerprint.username, - fingerprintHostname: currentFingerprint.hostname + fingerprintHostname: currentFingerprint.hostname, + timestamp: approvals.timestamp }) .from(approvals) .innerJoin(users, and(eq(approvals.userId, users.userId))) @@ -120,22 +157,12 @@ async function queryApprovals( ) .leftJoin(olms, eq(clients.clientId, olms.clientId)) .leftJoin(currentFingerprint, eq(olms.olmId, currentFingerprint.olmId)) - .where( - and( - eq(approvals.orgId, orgId), - sql`${approvals.decision} in ${state}`, - ...(clientId ? [eq(approvals.clientId, clientId)] : []) - ) - ) - .orderBy( - sql`CASE ${approvals.decision} WHEN 'pending' THEN 0 ELSE 1 END`, - desc(approvals.timestamp) - ) - .limit(limit) - .offset(offset); + .where(and(...conditions)) + .orderBy(desc(pendingSortKey), desc(approvals.timestamp)) + .limit(limit + 1); // the `+1` is used for the cursor // Process results to format device names and build fingerprint objects - return res.map((approval) => { + const approvalsList = res.slice(0, limit).map((approval) => { const model = approval.deviceModel || null; const deviceName = approval.clientName ? getUserDeviceName(model, approval.clientName) @@ -154,15 +181,15 @@ async function queryApprovals( const fingerprint = hasFingerprintData ? { - platform: approval.fingerprintPlatform || null, - osVersion: approval.fingerprintOsVersion || null, - kernelVersion: approval.fingerprintKernelVersion || null, - arch: approval.fingerprintArch || null, - deviceModel: approval.deviceModel || null, - serialNumber: approval.fingerprintSerialNumber || null, - username: approval.fingerprintUsername || null, - hostname: approval.fingerprintHostname || null - } + platform: approval.fingerprintPlatform ?? null, + osVersion: approval.fingerprintOsVersion ?? null, + kernelVersion: approval.fingerprintKernelVersion ?? null, + arch: approval.fingerprintArch ?? null, + deviceModel: approval.deviceModel ?? null, + serialNumber: approval.fingerprintSerialNumber ?? null, + username: approval.fingerprintUsername ?? null, + hostname: approval.fingerprintHostname ?? null + } : null; const { @@ -185,11 +212,30 @@ async function queryApprovals( niceId: approval.niceId || null }; }); + let nextCursorPending: number | null = null; + let nextCursorTimestamp: number | null = null; + if (res.length > limit) { + const lastItem = res[limit]; + nextCursorPending = lastItem.decision === "pending" ? 1 : 0; + nextCursorTimestamp = lastItem.timestamp; + } + return { + approvalsList, + nextCursorPending, + nextCursorTimestamp + }; } export type ListApprovalsResponse = { - approvals: NonNullable>>; - pagination: { total: number; limit: number; offset: number }; + approvals: NonNullable< + Awaited> + >["approvalsList"]; + pagination: { + total: number; + limit: number; + cursorPending: number | null; + cursorTimestamp: number | null; + }; }; export async function listApprovals( @@ -217,7 +263,13 @@ export async function listApprovals( ) ); } - const { limit, offset, approvalState, clientId } = parsedQuery.data; + const { + limit, + cursorPending, + cursorTimestamp, + approvalState, + clientId + } = parsedQuery.data; const { orgId } = parsedParams.data; @@ -234,13 +286,15 @@ export async function listApprovals( } } - const approvalsList = await queryApprovals( - orgId.toString(), - limit, - offset, - approvalState, - clientId - ); + const { approvalsList, nextCursorPending, nextCursorTimestamp } = + await queryApprovals({ + orgId: orgId.toString(), + limit, + cursorPending, + cursorTimestamp, + approvalState, + clientId + }); const [{ count }] = await db .select({ count: sql`count(*)` }) @@ -252,7 +306,8 @@ export async function listApprovals( pagination: { total: count, limit, - offset + cursorPending: nextCursorPending, + cursorTimestamp: nextCursorTimestamp } }, success: true, diff --git a/server/routers/client/listUserDevices.ts b/server/routers/client/listUserDevices.ts index 479d16a0..d152250b 100644 --- a/server/routers/client/listUserDevices.ts +++ b/server/routers/client/listUserDevices.ts @@ -6,7 +6,6 @@ import { olms, orgs, roleClients, - sites, userClients, users } from "@server/db"; @@ -25,7 +24,6 @@ import { inArray, isNotNull, isNull, - not, or, sql, type SQL diff --git a/src/components/ApprovalFeed.tsx b/src/components/ApprovalFeed.tsx index 4c6122c6..9abcbeed 100644 --- a/src/components/ApprovalFeed.tsx +++ b/src/components/ApprovalFeed.tsx @@ -2,23 +2,25 @@ import { useEnvContext } from "@app/hooks/useEnvContext"; import { toast } from "@app/hooks/useToast"; import { createApiClient, formatAxiosError } from "@app/lib/api"; -import { getUserDisplayName } from "@app/lib/getUserDisplayName"; import { cn } from "@app/lib/cn"; import { formatFingerprintInfo } from "@app/lib/formatDeviceFingerprint"; +import { getUserDisplayName } from "@app/lib/getUserDisplayName"; import { approvalFiltersSchema, approvalQueries, type ApprovalItem } from "@app/lib/queries"; -import { useQuery } from "@tanstack/react-query"; -import { ArrowRight, Ban, Check, LaptopMinimal, RefreshCw } from "lucide-react"; +import { useInfiniteQuery } from "@tanstack/react-query"; +import { Ban, Check, Loader, RefreshCw } from "lucide-react"; import { useTranslations } from "next-intl"; import Link from "next/link"; import { usePathname, useRouter, useSearchParams } from "next/navigation"; import { Fragment, useActionState } from "react"; +import { ApprovalsEmptyState } from "./ApprovalsEmptyState"; import { Badge } from "./ui/badge"; import { Button } from "./ui/button"; import { Card, CardHeader } from "./ui/card"; +import { InfoPopup } from "./ui/info-popup"; import { Label } from "./ui/label"; import { Select, @@ -28,8 +30,6 @@ import { SelectValue } from "./ui/select"; import { Separator } from "./ui/separator"; -import { InfoPopup } from "./ui/info-popup"; -import { ApprovalsEmptyState } from "./ApprovalsEmptyState"; export type ApprovalFeedProps = { orgId: string; @@ -50,11 +50,17 @@ export function ApprovalFeed({ Object.fromEntries(searchParams.entries()) ); - const { data, isFetching, refetch } = useQuery( - approvalQueries.listApprovals(orgId, filters) - ); + const { + data, + isFetching, + isLoading, + refetch, + hasNextPage, + fetchNextPage, + isFetchingNextPage + } = useInfiniteQuery(approvalQueries.listApprovals(orgId, filters)); - const approvals = data?.approvals ?? []; + const approvals = data?.pages.flatMap((data) => data.approvals) ?? []; // Show empty state if no approvals are enabled for any role if (!hasApprovalsEnabled) { @@ -110,13 +116,13 @@ export function ApprovalFeed({ onClick={() => { refetch(); }} - disabled={isFetching} + disabled={isFetching || isLoading} className="lg:static gap-2" > {t("refresh")} @@ -140,13 +146,30 @@ export function ApprovalFeed({ ))} {approvals.length === 0 && ( -
  • - {t("approvalListEmpty")} +
  • + {isLoading + ? t("loadingApprovals") + : t("approvalListEmpty")} + + {isLoading && ( + + )}
  • )} + {hasNextPage && ( + + )}
    ); } @@ -209,19 +232,19 @@ function ApprovalRequest({ approval, orgId, onSuccess }: ApprovalRequestProps) {   {approval.type === "user_device" && ( - {approval.deviceName ? ( - <> - {t("requestingNewDeviceApproval")}:{" "} - {approval.niceId ? ( - - {approval.deviceName} - - ) : ( - {approval.deviceName} - )} + {approval.deviceName ? ( + <> + {t("requestingNewDeviceApproval")}:{" "} + {approval.niceId ? ( + + {approval.deviceName} + + ) : ( + {approval.deviceName} + )} {approval.fingerprint && (
    @@ -229,7 +252,10 @@ function ApprovalRequest({ approval, orgId, onSuccess }: ApprovalRequestProps) { {t("deviceInformation")}
    - {formatFingerprintInfo(approval.fingerprint, t)} + {formatFingerprintInfo( + approval.fingerprint, + t + )}
    diff --git a/src/lib/queries.ts b/src/lib/queries.ts index 1d19c1e7..fe5350ff 100644 --- a/src/lib/queries.ts +++ b/src/lib/queries.ts @@ -16,11 +16,16 @@ import type { import type { ListTargetsResponse } from "@server/routers/target"; import type { ListUsersResponse } from "@server/routers/user"; import type ResponseT from "@server/types/Response"; -import { keepPreviousData, queryOptions } from "@tanstack/react-query"; +import { + infiniteQueryOptions, + keepPreviousData, + queryOptions +} from "@tanstack/react-query"; import type { AxiosResponse } from "axios"; import z from "zod"; import { remote } from "./api"; import { durationToMs } from "./durationToMs"; +import { wait } from "./wait"; export type ProductUpdate = { link: string | null; @@ -356,22 +361,50 @@ export const approvalQueries = { orgId: string, filters: z.infer ) => - queryOptions({ + infiniteQueryOptions({ queryKey: ["APPROVALS", orgId, filters] as const, - queryFn: async ({ signal, meta }) => { + queryFn: async ({ signal, pageParam, meta }) => { const sp = new URLSearchParams(); if (filters.approvalState) { sp.set("approvalState", filters.approvalState); } + if (pageParam) { + sp.set("cursorPending", pageParam.cursorPending.toString()); + sp.set( + "cursorTimestamp", + pageParam.cursorTimestamp.toString() + ); + } const res = await meta!.api.get< - AxiosResponse<{ approvals: ApprovalItem[] }> + AxiosResponse<{ + approvals: ApprovalItem[]; + pagination: { + total: number; + limit: number; + cursorPending: number | null; + cursorTimestamp: number | null; + }; + }> >(`/org/${orgId}/approvals?${sp.toString()}`, { signal }); return res.data.data; - } + }, + initialPageParam: null as { + cursorPending: number; + cursorTimestamp: number; + } | null, + placeholderData: keepPreviousData, + getNextPageParam: ({ pagination }) => + pagination.cursorPending != null && + pagination.cursorTimestamp != null + ? { + cursorPending: pagination.cursorPending, + cursorTimestamp: pagination.cursorTimestamp + } + : null }), pendingCount: (orgId: string) => queryOptions({ From 5b779ba9fe059389ab1e460dec9be258c768313a Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Tue, 10 Feb 2026 03:21:12 +0100 Subject: [PATCH 44/81] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ApprovalFeed.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ApprovalFeed.tsx b/src/components/ApprovalFeed.tsx index 9abcbeed..a158c1d4 100644 --- a/src/components/ApprovalFeed.tsx +++ b/src/components/ApprovalFeed.tsx @@ -167,7 +167,7 @@ export function ApprovalFeed({ loading={isFetchingNextPage} onClick={() => fetchNextPage()} > - {isFetchingNextPage ? t("loading") : t("approvalLoadMore")} + {t("approvalLoadMore")} )}
    From c94d246c24c8894ebfecd1e7aef8941de3b56804 Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Tue, 10 Feb 2026 04:00:45 +0100 Subject: [PATCH 45/81] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20list=20machine=20que?= =?UTF-8?q?ry?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/routers/client/listClients.ts | 98 ++++++++++++++++++++++++---- 1 file changed, 84 insertions(+), 14 deletions(-) diff --git a/server/routers/client/listClients.ts b/server/routers/client/listClients.ts index 588f1edd..9ba7c684 100644 --- a/server/routers/client/listClients.ts +++ b/server/routers/client/listClients.ts @@ -15,7 +15,18 @@ import logger from "@server/logger"; import { OpenAPITags, registry } from "@server/openApi"; import HttpCode from "@server/types/HttpCode"; import type { PaginatedResponse } from "@server/types/Pagination"; -import { and, eq, inArray, isNull, or, sql } from "drizzle-orm"; +import { + and, + asc, + desc, + eq, + ilike, + inArray, + isNull, + or, + sql, + type SQL +} from "drizzle-orm"; import { NextFunction, Request, Response } from "express"; import createHttpError from "http-errors"; import NodeCache from "node-cache"; @@ -97,7 +108,29 @@ const listClientsSchema = z.object({ .catch(1) .default(1), query: z.string().optional(), - sort_by: z.enum(["megabytesIn", "megabytesOut"]).optional().catch(undefined) + sort_by: z + .enum(["megabytesIn", "megabytesOut"]) + .optional() + .catch(undefined), + order: z.enum(["asc", "desc"]).optional().default("asc").catch("asc"), + online: z + .enum(["true", "false"]) + .transform((v) => v === "true") + .optional() + .catch(undefined), + status: z.preprocess( + (val: string | undefined) => { + if (val) { + return val.split(","); // the search query array is an array joined by commas + } + return undefined; + }, + z + .array(z.enum(["active", "blocked", "archived"])) + .optional() + .default(["active"]) + .catch(["active"]) + ) }); function queryClientsBase() { @@ -188,7 +221,8 @@ export async function listClients( ) ); } - const { page, pageSize, query } = parsedQuery.data; + const { page, pageSize, online, query, status, sort_by, order } = + parsedQuery.data; const parsedParams = listClientsParamsSchema.safeParse(req.params); if (!parsedParams.success) { @@ -237,24 +271,60 @@ export async function listClients( const accessibleClientIds = accessibleClients.map( (client) => client.clientId ); - const baseQuery = queryClientsBase(); // Get client count with filter const conditions = [ - inArray(clients.clientId, accessibleClientIds), - eq(clients.orgId, orgId), - isNull(clients.userId) + and( + inArray(clients.clientId, accessibleClientIds), + eq(clients.orgId, orgId), + isNull(clients.userId) + ) ]; - const countQuery = db.$count( - queryClientsBase().where(and(...conditions)) - ); + if (typeof online !== "undefined") { + conditions.push(eq(clients.online, online)); + } + + if (status.length > 0) { + const filterAggregates: (SQL | undefined)[] = []; + + if (status.includes("active")) { + filterAggregates.push( + and(eq(clients.archived, false), eq(clients.blocked, false)) + ); + } + + if (status.includes("archived")) { + filterAggregates.push(eq(clients.archived, true)); + } + if (status.includes("blocked")) { + filterAggregates.push(eq(clients.blocked, true)); + } + + conditions.push(or(...filterAggregates)); + } + + if (query) { + conditions.push(or(ilike(clients.name, "%" + query + "%"))); + } + + const baseQuery = queryClientsBase().where(and(...conditions)); + + const countQuery = db.$count(baseQuery.as("filtered_clients")); + + const listMachinesQuery = baseQuery + .limit(page) + .offset(pageSize * (page - 1)) + .orderBy( + sort_by + ? order === "asc" + ? asc(clients[sort_by]) + : desc(clients[sort_by]) + : asc(clients.clientId) + ); const [clientsList, totalCount] = await Promise.all([ - baseQuery - .where(and(...conditions)) - .limit(page) - .offset(pageSize * (page - 1)), + listMachinesQuery, countQuery ]); From d6ade102dc084f1f51dfadac1c8e20135c5582c1 Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Tue, 10 Feb 2026 05:14:37 +0100 Subject: [PATCH 46/81] =?UTF-8?q?=E2=9C=A8=20filter=20&=20paginate=20on=20?= =?UTF-8?q?machine=20clients=20table?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/routers/client/listUserDevices.ts | 4 +- .../[orgId]/settings/clients/machine/page.tsx | 20 +- src/components/MachineClientsTable.tsx | 260 ++++++++++-------- src/components/ProxyResourcesTable.tsx | 8 +- src/components/UserDevicesTable.tsx | 8 +- 5 files changed, 176 insertions(+), 124 deletions(-) diff --git a/server/routers/client/listUserDevices.ts b/server/routers/client/listUserDevices.ts index d152250b..65dba7e6 100644 --- a/server/routers/client/listUserDevices.ts +++ b/server/routers/client/listUserDevices.ts @@ -124,6 +124,7 @@ const listUserDevicesSchema = z.object({ "windows", "android", "cli", + "olm", "macos", "ios", "ipados", @@ -302,7 +303,8 @@ export async function listUserDevices( ios: "Pangolin iOS", ipados: "Pangolin iPadOS", macos: "Pangolin macOS", - cli: "Pangolin CLI" + cli: "Pangolin CLI", + olm: "Olm CLI" } satisfies Record< Exclude, string diff --git a/src/app/[orgId]/settings/clients/machine/page.tsx b/src/app/[orgId]/settings/clients/machine/page.tsx index b3e731e8..4b40c906 100644 --- a/src/app/[orgId]/settings/clients/machine/page.tsx +++ b/src/app/[orgId]/settings/clients/machine/page.tsx @@ -7,10 +7,11 @@ import { authCookieHeader } from "@app/lib/api/cookies"; import { ListClientsResponse } from "@server/routers/client"; import { AxiosResponse } from "axios"; import { getTranslations } from "next-intl/server"; +import type { Pagination } from "@server/types/Pagination"; type ClientsPageProps = { params: Promise<{ orgId: string }>; - searchParams: Promise<{ view?: string }>; + searchParams: Promise>; }; export const dynamic = "force-dynamic"; @@ -19,17 +20,25 @@ export default async function ClientsPage(props: ClientsPageProps) { const t = await getTranslations(); const params = await props.params; + const searchParams = new URLSearchParams(await props.searchParams); let machineClients: ListClientsResponse["clients"] = []; + let pagination: Pagination = { + page: 1, + total: 0, + pageSize: 20 + }; try { const machineRes = await internal.get< AxiosResponse >( - `/org/${params.orgId}/clients?filter=machine`, + `/org/${params.orgId}/clients?${searchParams.toString()}`, await authCookieHeader() ); - machineClients = machineRes.data.data.clients; + const responseData = machineRes.data.data; + machineClients = responseData.clients; + pagination = responseData.pagination; } catch (e) {} function formatSize(mb: number): string { @@ -80,6 +89,11 @@ export default async function ClientsPage(props: ClientsPageProps) { ); diff --git a/src/components/MachineClientsTable.tsx b/src/components/MachineClientsTable.tsx index ad01c40f..5af44dd5 100644 --- a/src/components/MachineClientsTable.tsx +++ b/src/components/MachineClientsTable.tsx @@ -16,13 +16,23 @@ import { ArrowRight, ArrowUpDown, MoreHorizontal, - CircleSlash + CircleSlash, + ArrowDown01Icon, + ArrowUp10Icon, + ChevronsUpDownIcon } from "lucide-react"; import { useTranslations } from "next-intl"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { useMemo, useState, useTransition } from "react"; import { Badge } from "./ui/badge"; +import type { PaginationState } from "@tanstack/react-table"; +import { ControlledDataTable } from "./ui/controlled-data-table"; +import { useNavigationContext } from "@app/hooks/useNavigationContext"; +import { useDebouncedCallback } from "use-debounce"; +import z from "zod"; +import { getNextSortOrder, getSortDirection } from "@app/lib/sortColumn"; +import { ColumnFilterButton } from "./ColumnFilterButton"; export type ClientRow = { id: number; @@ -48,14 +58,24 @@ export type ClientRow = { type ClientTableProps = { machineClients: ClientRow[]; orgId: string; + pagination: PaginationState; + rowCount: number; }; export default function MachineClientsTable({ machineClients, - orgId + orgId, + pagination, + rowCount }: ClientTableProps) { const router = useRouter(); + const { + navigate: filter, + isNavigating: isFiltering, + searchParams + } = useNavigationContext(); + const t = useTranslations(); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); @@ -65,6 +85,7 @@ export default function MachineClientsTable({ const api = createApiClient(useEnvContext()); const [isRefreshing, startTransition] = useTransition(); + const [isNavigatingToAddPage, startNavigation] = useTransition(); const defaultMachineColumnVisibility = { subnet: false, @@ -182,22 +203,8 @@ export default function MachineClientsTable({ { accessorKey: "name", enableHiding: false, - friendlyName: "Name", - header: ({ column }) => { - return ( - - ); - }, + friendlyName: t("name"), + header: () => {t("name")}, cell: ({ row }) => { const r = row.original; return ( @@ -224,38 +231,35 @@ export default function MachineClientsTable({ { accessorKey: "niceId", friendlyName: "Identifier", - header: ({ column }) => { - return ( - - ); - } + header: () => {t("identifier")} }, { accessorKey: "online", - friendlyName: "Connectivity", - header: ({ column }) => { + friendlyName: t("online"), + header: () => { return ( - + onValueChange={(value) => + handleFilterChange("online", value) + } + searchPlaceholder={t("searchPlaceholder")} + emptyMessage={t("emptySearchOptions")} + label={t("online")} + className="p-3" + /> ); }, cell: ({ row }) => { @@ -279,38 +283,52 @@ export default function MachineClientsTable({ }, { accessorKey: "mbIn", - friendlyName: "Data In", - header: ({ column }) => { + friendlyName: t("dataIn"), + header: () => { + const dataInOrder = getSortDirection( + "megabytesIn", + searchParams + ); + + const Icon = + dataInOrder === "asc" + ? ArrowDown01Icon + : dataInOrder === "desc" + ? ArrowUp10Icon + : ChevronsUpDownIcon; return ( ); } }, { accessorKey: "mbOut", - friendlyName: "Data Out", - header: ({ column }) => { + friendlyName: t("dataOut"), + header: () => { + const dataOutOrder = getSortDirection( + "megabytesOut", + searchParams + ); + + const Icon = + dataOutOrder === "asc" + ? ArrowDown01Icon + : dataOutOrder === "desc" + ? ArrowUp10Icon + : ChevronsUpDownIcon; return ( ); } @@ -356,22 +374,8 @@ export default function MachineClientsTable({ }, { accessorKey: "subnet", - friendlyName: "Address", - header: ({ column }) => { - return ( - - ); - } + friendlyName: t("address"), + header: () => {t("address")} } ]; @@ -455,7 +459,56 @@ export default function MachineClientsTable({ } return baseColumns; - }, [hasRowsWithoutUserId, t]); + }, [hasRowsWithoutUserId, t, getSortDirection, toggleSort]); + + const booleanSearchFilterSchema = z + .enum(["true", "false"]) + .optional() + .catch(undefined); + + function handleFilterChange( + column: string, + value: string | null | undefined | string[] + ) { + searchParams.delete(column); + searchParams.delete("page"); + + if (typeof value === "string") { + searchParams.set(column, value); + } else if (value) { + for (const val of value) { + searchParams.append(column, val); + } + } + + filter({ + searchParams + }); + } + + function toggleSort(column: string) { + const newSearch = getNextSortOrder(column, searchParams); + + filter({ + searchParams: newSearch + }); + } + + const handlePaginationChange = (newPage: PaginationState) => { + searchParams.set("page", (newPage.pageIndex + 1).toString()); + searchParams.set("pageSize", newPage.pageSize.toString()); + filter({ + searchParams + }); + }; + + const handleSearchChange = useDebouncedCallback((query: string) => { + searchParams.set("query", query); + searchParams.delete("page"); + filter({ + searchParams + }); + }, 300); return ( <> @@ -478,20 +531,25 @@ export default function MachineClientsTable({ title="Delete Client" /> )} - - router.push(`/${orgId}/settings/clients/machine/create`) + startNavigation(() => + router.push(`/${orgId}/settings/clients/machine/create`) + ) } + pagination={pagination} + rowCount={rowCount} addButtonText={t("createClient")} onRefresh={refreshData} - isRefreshing={isRefreshing} - enableColumnVisibility={true} - persistColumnVisibility="machine-clients" + isRefreshing={isRefreshing || isFiltering} + onSearch={handleSearchChange} + onPaginationChange={handlePaginationChange} + isNavigatingToAddPage={isNavigatingToAddPage} + enableColumnVisibility columnVisibility={defaultMachineColumnVisibility} stickyLeftColumn="name" stickyRightColumn="actions" @@ -518,30 +576,10 @@ export default function MachineClientsTable({ value: "blocked" } ], - filterFn: ( - row: ClientRow, - selectedValues: (string | number | boolean)[] - ) => { - if (selectedValues.length === 0) return true; - const rowArchived = row.archived || false; - const rowBlocked = row.blocked || false; - const isActive = !rowArchived && !rowBlocked; - - if (selectedValues.includes("active") && isActive) - return true; - if ( - selectedValues.includes("archived") && - rowArchived - ) - return true; - if ( - selectedValues.includes("blocked") && - rowBlocked - ) - return true; - return false; + onValueChange(selectedValues: string[]) { + handleFilterChange("status", selectedValues); }, - defaultValues: ["active"] // Default to showing active clients + values: searchParams.getAll("status") } ]} /> diff --git a/src/components/ProxyResourcesTable.tsx b/src/components/ProxyResourcesTable.tsx index ba69dec4..490904c7 100644 --- a/src/components/ProxyResourcesTable.tsx +++ b/src/components/ProxyResourcesTable.tsx @@ -611,11 +611,9 @@ export default function ProxyResourcesTable({ onSearch={handleSearchChange} onPaginationChange={handlePaginationChange} onAdd={() => - startNavigation(() => { - router.push( - `/${orgId}/settings/resources/proxy/create` - ); - }) + startNavigation(() => + router.push(`/${orgId}/settings/resources/proxy/create`) + ) } addButtonText={t("resourceAdd")} onRefresh={refreshData} diff --git a/src/components/UserDevicesTable.tsx b/src/components/UserDevicesTable.tsx index 642ef1f7..1b7b0c69 100644 --- a/src/components/UserDevicesTable.tsx +++ b/src/components/UserDevicesTable.tsx @@ -443,10 +443,6 @@ export default function UserDevicesTable({ searchParams ); - console.log({ - dataInOrder, - searchParams: Object.fromEntries(searchParams.entries()) - }); const Icon = dataInOrder === "asc" ? ArrowDown01Icon @@ -520,6 +516,10 @@ export default function UserDevicesTable({ value: "cli", label: "Pangolin CLI" }, + { + value: "olm", + label: "Olm CLI" + }, { value: "unknown", label: t("unknown") From d5b6de70dad469cd389e581489c79290de81aeab Mon Sep 17 00:00:00 2001 From: Lokowitz Date: Tue, 10 Feb 2026 11:49:53 +0000 Subject: [PATCH 47/81] update package and update dockerfile --- .dockerignore | 3 +- Dockerfile | 63 ++++++------ package-lock.json | 237 ++++++++++++++++++++-------------------------- package.json | 8 +- 4 files changed, 141 insertions(+), 170 deletions(-) diff --git a/.dockerignore b/.dockerignore index ecd919cd..27e70883 100644 --- a/.dockerignore +++ b/.dockerignore @@ -31,4 +31,5 @@ dist migrations/ config/ build.ts -tsconfig.json \ No newline at end of file +tsconfig.json +Dockerfile* diff --git a/Dockerfile b/Dockerfile index f82719a6..4830067e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,33 +1,54 @@ -FROM node:24-alpine AS builder +FROM node:24-alpine AS base WORKDIR /app -ARG BUILD=oss -ARG DATABASE=sqlite - RUN apk add --no-cache python3 make g++ -# COPY package.json package-lock.json ./ COPY package*.json ./ + +FROM base AS builder-dev + RUN npm ci COPY . . +ARG BUILD=oss +ARG DATABASE=sqlite + RUN if [ "$BUILD" = "oss" ]; then rm -rf server/private; fi && \ npm run set:$DATABASE && \ npm run set:$BUILD && \ npm run db:generate && \ npm run build && \ - npm run build:cli + npm run build:cli && \ + test -f dist/server.mjs -# test to make sure the build output is there and error if not -RUN test -f dist/server.mjs +FROM base AS builder -# Prune dev dependencies and clean up to prepare for copy to runner -RUN npm prune --omit=dev && npm cache clean --force +RUN npm ci --omit=dev FROM node:24-alpine AS runner +WORKDIR /app + +RUN apk add --no-cache curl tzdata + +COPY --from=builder /app/node_modules ./node_modules +COPY --from=builder /app/package.json ./package.json + +COPY --from=builder-dev /app/.next/standalone ./ +COPY --from=builder-dev /app/.next/static ./.next/static +COPY --from=builder-dev /app/dist ./dist +COPY --from=builder-dev /app/server/migrations ./dist/init + +COPY ./cli/wrapper.sh /usr/local/bin/pangctl +RUN chmod +x /usr/local/bin/pangctl ./dist/cli.mjs + +COPY server/db/names.json ./dist/names.json +COPY server/db/ios_models.json ./dist/ios_models.json +COPY server/db/mac_models.json ./dist/mac_models.json +COPY public ./public + # OCI Image Labels - Build Args for dynamic values ARG VERSION="dev" ARG REVISION="" @@ -38,28 +59,6 @@ ARG LICENSE="AGPL-3.0" ARG IMAGE_TITLE="Pangolin" ARG IMAGE_DESCRIPTION="Identity-aware VPN and proxy for remote access to anything, anywhere" -WORKDIR /app - -# Only curl and tzdata needed at runtime - no build tools! -RUN apk add --no-cache curl tzdata - -# Copy pre-built node_modules from builder (already pruned to production only) -# This includes the compiled native modules like better-sqlite3 -COPY --from=builder /app/node_modules ./node_modules -COPY --from=builder /app/.next/standalone ./ -COPY --from=builder /app/.next/static ./.next/static -COPY --from=builder /app/dist ./dist -COPY --from=builder /app/server/migrations ./dist/init -COPY --from=builder /app/package.json ./package.json - -COPY ./cli/wrapper.sh /usr/local/bin/pangctl -RUN chmod +x /usr/local/bin/pangctl ./dist/cli.mjs - -COPY server/db/names.json ./dist/names.json -COPY server/db/ios_models.json ./dist/ios_models.json -COPY server/db/mac_models.json ./dist/mac_models.json -COPY public ./public - # OCI Image Labels # https://github.com/opencontainers/image-spec/blob/main/annotations.md LABEL org.opencontainers.image.source="https://github.com/fosrl/pangolin" \ diff --git a/package-lock.json b/package-lock.json index be7656e7..b111d659 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "SEE LICENSE IN LICENSE AND README.md", "dependencies": { "@asteasolutions/zod-to-openapi": "8.4.0", - "@aws-sdk/client-s3": "3.985.0", + "@aws-sdk/client-s3": "3.986.0", "@faker-js/faker": "10.3.0", "@headlessui/react": "2.2.9", "@hookform/resolvers": "5.2.2", @@ -77,10 +77,10 @@ "nodemailer": "8.0.1", "oslo": "1.2.1", "pg": "8.18.0", - "posthog-node": "5.24.13", + "posthog-node": "5.24.14", "qrcode.react": "4.2.0", "react": "19.2.4", - "react-day-picker": "9.13.1", + "react-day-picker": "9.13.2", "react-dom": "19.2.4", "react-easy-sort": "1.8.0", "react-hook-form": "7.71.1", @@ -145,7 +145,7 @@ "tsc-alias": "1.8.16", "tsx": "4.21.0", "typescript": "5.9.3", - "typescript-eslint": "8.54.0" + "typescript-eslint": "8.55.0" } }, "node_modules/@alloc/quick-lru": { @@ -390,10 +390,9 @@ } }, "node_modules/@aws-sdk/client-s3": { - "version": "3.985.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.985.0.tgz", - "integrity": "sha512-S9TqjzzZEEIKBnC7yFpvqM7CG9ALpY5qhQ5BnDBJtdG20NoGpjKLGUUfD2wmZItuhbrcM4Z8c6m6Fg0XYIOVvw==", - "license": "Apache-2.0", + "version": "3.986.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.986.0.tgz", + "integrity": "sha512-IcDJ8shVVvbxgMe8+dLWcv6uhSwmX65PHTVGX81BhWAElPnp3CL8w/5uzOPRo4n4/bqIk9eskGVEIicw2o+SrA==", "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", @@ -411,9 +410,9 @@ "@aws-sdk/middleware-ssec": "^3.972.3", "@aws-sdk/middleware-user-agent": "^3.972.7", "@aws-sdk/region-config-resolver": "^3.972.3", - "@aws-sdk/signature-v4-multi-region": "3.985.0", + "@aws-sdk/signature-v4-multi-region": "3.986.0", "@aws-sdk/types": "^3.973.1", - "@aws-sdk/util-endpoints": "3.985.0", + "@aws-sdk/util-endpoints": "3.986.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", "@aws-sdk/util-user-agent-node": "^3.972.5", "@smithy/config-resolver": "^4.4.6", @@ -455,6 +454,21 @@ "node": ">=20.0.0" } }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/util-endpoints": { + "version": "3.986.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.986.0.tgz", + "integrity": "sha512-Mqi79L38qi1gCG3adlVdbNrSxvcm1IPDLiJPA3OBypY5ewxUyWbaA3DD4goG+EwET6LSFgZJcRSIh6KBNpP5pA==", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-endpoints": "^3.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/client-sso": { "version": "3.985.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.985.0.tgz", @@ -820,7 +834,6 @@ "version": "3.972.7", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.7.tgz", "integrity": "sha512-VtZ7tMIw18VzjG+I6D6rh2eLkJfTtByiFoCIauGDtTTPBEUMQUiGaJ/zZrPlCY6BsvLLeFKz3+E5mntgiOWmIg==", - "license": "Apache-2.0", "dependencies": { "@aws-sdk/core": "^3.973.7", "@aws-sdk/types": "^3.973.1", @@ -939,10 +952,9 @@ } }, "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.985.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.985.0.tgz", - "integrity": "sha512-W6hTSOPiSbh4IdTYVxN7xHjpCh0qvfQU1GKGBzGQm0ZEIOaMmWqiDEvFfyGYKmfBvumT8vHKxQRTX0av9omtIg==", - "license": "Apache-2.0", + "version": "3.986.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.986.0.tgz", + "integrity": "sha512-Upw+rw7wCH93E6QWxqpAqJLrUmJYVUAWrk4tCOBnkeuwzGERZvJFL5UQ6TAJFj9T18Ih+vNFaACh8J5aP4oTBw==", "dependencies": { "@aws-sdk/middleware-sdk-s3": "^3.972.7", "@aws-sdk/types": "^3.973.1", @@ -1116,7 +1128,6 @@ "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.26.2", @@ -3558,7 +3569,6 @@ "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": "^14.21.3 || >=16" }, @@ -4691,9 +4701,9 @@ } }, "node_modules/@posthog/core": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/@posthog/core/-/core-1.20.2.tgz", - "integrity": "sha512-aQhrUzOHYr0z/bkwDsJ5hXahdh6oyeWdx2/CHwR6vFG3eK07J69lbuGOj+HGOOxJP1eAdNnsk8J0fj1vqRA9+A==", + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/@posthog/core/-/core-1.21.0.tgz", + "integrity": "sha512-0a2JUIX1vhduP2El/6/J8s5AeYAurIoufQGFgMiGnJE5ajd63o9LFocu2vFYYBnIOmy75y4ADNeW8zSl1keEQQ==", "dependencies": { "cross-spawn": "^7.0.6" } @@ -7988,7 +7998,6 @@ "resolved": "https://registry.npmjs.org/@react-email/text/-/text-0.1.6.tgz", "integrity": "sha512-TYqkioRS45wTR5il3dYk/SbUjjEdhSwh9BtRNB99qNH1pXAwA45H7rAuxehiu8iJQJH0IyIr+6n62gBz9ezmsw==", "license": "MIT", - "peer": true, "engines": { "node": ">=20.0.0" }, @@ -9436,7 +9445,6 @@ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.20.tgz", "integrity": "sha512-vXBxa+qeyveVO7OA0jX1z+DeyCA4JKnThKv411jd5SORpBKgkcVnYKCiBgECvADvniBX7tobwBmg01qq9JmMJw==", "license": "MIT", - "peer": true, "dependencies": { "@tanstack/query-core": "5.90.20" }, @@ -9542,7 +9550,6 @@ "integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@types/node": "*" } @@ -9883,7 +9890,6 @@ "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^5.0.0", @@ -9978,7 +9984,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.2.tgz", "integrity": "sha512-BkmoP5/FhRYek5izySdkOneRyXYN35I860MFAGupTdebyE66uZaR+bXLHq8k4DirE5DwQi3NuhvRU1jqTVwUrQ==", "devOptional": true, - "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -10006,7 +10011,6 @@ "integrity": "sha512-RmhMd/wD+CF8Dfo+cVIy3RR5cl8CyfXQ0tGgW6XBL8L4LM/UTEbNXYRbLwU6w+CgrKBNbrQWt4FUtTfaU5jSYQ==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@types/node": "*", "pg-protocol": "*", @@ -10033,7 +10037,6 @@ "integrity": "sha512-KkiJeU6VbYbUOp5ITMIc7kBfqlYkKA5KhEHVrGMmUUMt7NeaZg65ojdPk+FtNrBAOXNVM5QM72jnADjM+XVRAQ==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -10044,7 +10047,6 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "devOptional": true, "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -10120,7 +10122,8 @@ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", "license": "MIT", - "optional": true + "optional": true, + "peer": true }, "node_modules/@types/ws": { "version": "8.18.1", @@ -10150,17 +10153,16 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.54.0.tgz", - "integrity": "sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.55.0.tgz", + "integrity": "sha512-1y/MVSz0NglV1ijHC8OT49mPJ4qhPYjiK08YUQVbIOyu+5k862LKUHFkpKHWu//zmr7hDR2rhwUm6gnCGNmGBQ==", "dev": true, - "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.54.0", - "@typescript-eslint/type-utils": "8.54.0", - "@typescript-eslint/utils": "8.54.0", - "@typescript-eslint/visitor-keys": "8.54.0", + "@typescript-eslint/scope-manager": "8.55.0", + "@typescript-eslint/type-utils": "8.55.0", + "@typescript-eslint/utils": "8.55.0", + "@typescript-eslint/visitor-keys": "8.55.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" @@ -10173,7 +10175,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.54.0", + "@typescript-eslint/parser": "^8.55.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } @@ -10183,23 +10185,20 @@ "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", "dev": true, - "license": "MIT", "engines": { "node": ">= 4" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.54.0.tgz", - "integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.55.0.tgz", + "integrity": "sha512-4z2nCSBfVIMnbuu8uinj+f0o4qOeggYJLbjpPHka3KH1om7e+H9yLKTYgksTaHcGco+NClhhY2vyO3HsMH1RGw==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.54.0", - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/typescript-estree": "8.54.0", - "@typescript-eslint/visitor-keys": "8.54.0", + "@typescript-eslint/scope-manager": "8.55.0", + "@typescript-eslint/types": "8.55.0", + "@typescript-eslint/typescript-estree": "8.55.0", + "@typescript-eslint/visitor-keys": "8.55.0", "debug": "^4.4.3" }, "engines": { @@ -10215,14 +10214,13 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.54.0.tgz", - "integrity": "sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.55.0.tgz", + "integrity": "sha512-zRcVVPFUYWa3kNnjaZGXSu3xkKV1zXy8M4nO/pElzQhFweb7PPtluDLQtKArEOGmjXoRjnUZ29NjOiF0eCDkcQ==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.54.0", - "@typescript-eslint/types": "^8.54.0", + "@typescript-eslint/tsconfig-utils": "^8.55.0", + "@typescript-eslint/types": "^8.55.0", "debug": "^4.4.3" }, "engines": { @@ -10237,14 +10235,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.54.0.tgz", - "integrity": "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.55.0.tgz", + "integrity": "sha512-fVu5Omrd3jeqeQLiB9f1YsuK/iHFOwb04bCtY4BSCLgjNbOD33ZdV6KyEqplHr+IlpgT0QTZ/iJ+wT7hvTx49Q==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/visitor-keys": "8.54.0" + "@typescript-eslint/types": "8.55.0", + "@typescript-eslint/visitor-keys": "8.55.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -10255,11 +10252,10 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.54.0.tgz", - "integrity": "sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.55.0.tgz", + "integrity": "sha512-1R9cXqY7RQd7WuqSN47PK9EDpgFUK3VqdmbYrvWJZYDd0cavROGn+74ktWBlmJ13NXUQKlZ/iAEQHI/V0kKe0Q==", "dev": true, - "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -10272,15 +10268,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.54.0.tgz", - "integrity": "sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.55.0.tgz", + "integrity": "sha512-x1iH2unH4qAt6I37I2CGlsNs+B9WGxurP2uyZLRz6UJoZWDBx9cJL1xVN/FiOmHEONEg6RIufdvyT0TEYIgC5g==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/typescript-estree": "8.54.0", - "@typescript-eslint/utils": "8.54.0", + "@typescript-eslint/types": "8.55.0", + "@typescript-eslint/typescript-estree": "8.55.0", + "@typescript-eslint/utils": "8.55.0", "debug": "^4.4.3", "ts-api-utils": "^2.4.0" }, @@ -10297,11 +10292,10 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.54.0.tgz", - "integrity": "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.55.0.tgz", + "integrity": "sha512-ujT0Je8GI5BJWi+/mMoR0wxwVEQaxM+pi30xuMiJETlX80OPovb2p9E8ss87gnSVtYXtJoU9U1Cowcr6w2FE0w==", "dev": true, - "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -10311,16 +10305,15 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.54.0.tgz", - "integrity": "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.55.0.tgz", + "integrity": "sha512-EwrH67bSWdx/3aRQhCoxDaHM+CrZjotc2UCCpEDVqfCE+7OjKAGWNY2HsCSTEVvWH2clYQK8pdeLp42EVs+xQw==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.54.0", - "@typescript-eslint/tsconfig-utils": "8.54.0", - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/visitor-keys": "8.54.0", + "@typescript-eslint/project-service": "8.55.0", + "@typescript-eslint/tsconfig-utils": "8.55.0", + "@typescript-eslint/types": "8.55.0", + "@typescript-eslint/visitor-keys": "8.55.0", "debug": "^4.4.3", "minimatch": "^9.0.5", "semver": "^7.7.3", @@ -10343,7 +10336,6 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, - "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -10353,7 +10345,6 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, - "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -10365,16 +10356,15 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.54.0.tgz", - "integrity": "sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.55.0.tgz", + "integrity": "sha512-BqZEsnPGdYpgyEIkDC1BadNY8oMwckftxBT+C8W0g1iKPdeqKZBtTfnvcq0nf60u7MkjFO8RBvpRGZBPw4L2ow==", "dev": true, - "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.54.0", - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/typescript-estree": "8.54.0" + "@typescript-eslint/scope-manager": "8.55.0", + "@typescript-eslint/types": "8.55.0", + "@typescript-eslint/typescript-estree": "8.55.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -10389,13 +10379,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.54.0.tgz", - "integrity": "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.55.0.tgz", + "integrity": "sha512-AxNRwEie8Nn4eFS1FzDMJWIISMGoXMb037sgCBJ3UR6o0fQTzr2tqN9WT+DkWJPhIdQCfV7T6D387566VtnCJA==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/types": "8.55.0", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -10705,7 +10694,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -11152,7 +11140,6 @@ "integrity": "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/types": "^7.26.0" } @@ -11210,7 +11197,6 @@ "integrity": "sha512-Ba0KR+Fzxh2jDRhdg6TSH0SJGzb8C0aBY4hR8w8madIdIzzC6Y1+kx5qR6eS1Z+Gy20h6ZU28aeyg0z1VIrShQ==", "hasInstallScript": true, "license": "MIT", - "peer": true, "dependencies": { "bindings": "^1.5.0", "prebuild-install": "^7.1.1" @@ -11338,7 +11324,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -12292,7 +12277,6 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", - "peer": true, "engines": { "node": ">=12" } @@ -12721,6 +12705,7 @@ "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz", "integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==", "license": "(MPL-2.0 OR Apache-2.0)", + "peer": true, "optionalDependencies": { "@types/trusted-types": "^2.0.7" } @@ -13840,7 +13825,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -13939,7 +13923,6 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -14125,7 +14108,6 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -14445,7 +14427,6 @@ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", - "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", @@ -17019,6 +17000,7 @@ "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz", "integrity": "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==", "license": "MIT", + "peer": true, "dependencies": { "dompurify": "3.2.7", "marked": "14.0.0" @@ -17029,6 +17011,7 @@ "resolved": "https://registry.npmjs.org/marked/-/marked-14.0.0.tgz", "integrity": "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==", "license": "MIT", + "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -17116,7 +17099,6 @@ "version": "15.5.12", "resolved": "https://registry.npmjs.org/next/-/next-15.5.12.tgz", "integrity": "sha512-Fi/wQ4Etlrn60rz78bebG1i1SR20QxvV8tVp6iJspjLUSHcZoeUXCt+vmWoEcza85ElZzExK/jJ/F6SvtGktjA==", - "peer": true, "dependencies": { "@next/env": "15.5.12", "@swc/helpers": "0.5.15", @@ -18051,7 +18033,6 @@ "resolved": "https://registry.npmjs.org/pg/-/pg-8.18.0.tgz", "integrity": "sha512-xqrUDL1b9MbkydY/s+VZ6v+xiMUmOUk7SS9d/1kpyQxoJ6U9AO1oIJyUWVZojbfe5Cc/oluutcgFG4L9RDP1iQ==", "license": "MIT", - "peer": true, "dependencies": { "pg-connection-string": "^2.11.0", "pg-pool": "^3.11.0", @@ -18265,11 +18246,11 @@ } }, "node_modules/posthog-node": { - "version": "5.24.13", - "resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-5.24.13.tgz", - "integrity": "sha512-JHV2wUG5pkj50aV7XrHOWB7MZjqOsn+SKmbi1Q/Zy/94xvpmRt6mYDrF9Wp0HZTTUczyuqrXqK2eebgKJqlSzg==", + "version": "5.24.14", + "resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-5.24.14.tgz", + "integrity": "sha512-KbOQAZ66V9t4Abh/x62pL/2n504HlxQEavFZjpcyIpVwQEPmmafsoLU5ueL47m3i6m6r619Z76m4uyoxVfGqsA==", "dependencies": { - "@posthog/core": "1.20.2" + "@posthog/core": "1.21.0" }, "engines": { "node": "^20.20.0 || >=22.22.0" @@ -18550,16 +18531,14 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } }, "node_modules/react-day-picker": { - "version": "9.13.1", - "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-9.13.1.tgz", - "integrity": "sha512-9nx2lBBJ0VZw5jJekId3DishwnJLiqY1Me1JvCrIyqbWwcflBTVaEkiK+w1bre5oMNWYo722eu+8UAMXWMqktw==", - "license": "MIT", + "version": "9.13.2", + "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-9.13.2.tgz", + "integrity": "sha512-IMPiXfXVIAuR5Yk58DDPBC8QKClrhdXV+Tr/alBrwrHUw0qDDYB1m5zPNuTnnPIr/gmJ4ChMxmtqPdxm8+R4Eg==", "dependencies": { "@date-fns/tz": "^1.4.1", "date-fns": "^4.1.0", @@ -18581,7 +18560,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -19398,7 +19376,6 @@ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.71.1.tgz", "integrity": "sha512-9SUJKCGKo8HUSsCO+y0CtqkqI5nNuaDqTxyqPsZPqIwudpj4rCrAz/jZV+jn57bx5gtZKOh3neQu94DXMc+w5w==", "license": "MIT", - "peer": true, "engines": { "node": ">=18.0.0" }, @@ -20933,8 +20910,7 @@ "version": "4.1.18", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/tapable": { "version": "2.3.0", @@ -21090,7 +21066,6 @@ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", "dev": true, - "license": "MIT", "engines": { "node": ">=18.12" }, @@ -21412,7 +21387,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -21422,16 +21396,15 @@ } }, "node_modules/typescript-eslint": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.54.0.tgz", - "integrity": "sha512-CKsJ+g53QpsNPqbzUsfKVgd3Lny4yKZ1pP4qN3jdMOg/sisIDLGyDMezycquXLE5JsEU0wp3dGNdzig0/fmSVQ==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.55.0.tgz", + "integrity": "sha512-HE4wj+r5lmDVS9gdaN0/+iqNvPZwGfnJ5lZuz7s5vLlg9ODw0bIiiETaios9LvFI1U94/VBXGm3CB2Y5cNFMpw==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.54.0", - "@typescript-eslint/parser": "8.54.0", - "@typescript-eslint/typescript-estree": "8.54.0", - "@typescript-eslint/utils": "8.54.0" + "@typescript-eslint/eslint-plugin": "8.55.0", + "@typescript-eslint/parser": "8.55.0", + "@typescript-eslint/typescript-estree": "8.55.0", + "@typescript-eslint/utils": "8.55.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -21834,7 +21807,6 @@ "resolved": "https://registry.npmjs.org/winston/-/winston-3.19.0.tgz", "integrity": "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==", "license": "MIT", - "peer": true, "dependencies": { "@colors/colors": "^1.6.0", "@dabh/diagnostics": "^2.0.8", @@ -22136,7 +22108,6 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/package.json b/package.json index b999bb20..361310e1 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ }, "dependencies": { "@asteasolutions/zod-to-openapi": "8.4.0", - "@aws-sdk/client-s3": "3.985.0", + "@aws-sdk/client-s3": "3.986.0", "@faker-js/faker": "10.3.0", "@headlessui/react": "2.2.9", "@hookform/resolvers": "5.2.2", @@ -101,10 +101,10 @@ "nodemailer": "8.0.1", "oslo": "1.2.1", "pg": "8.18.0", - "posthog-node": "5.24.13", + "posthog-node": "5.24.14", "qrcode.react": "4.2.0", "react": "19.2.4", - "react-day-picker": "9.13.1", + "react-day-picker": "9.13.2", "react-dom": "19.2.4", "react-easy-sort": "1.8.0", "react-hook-form": "7.71.1", @@ -169,6 +169,6 @@ "tsc-alias": "1.8.16", "tsx": "4.21.0", "typescript": "5.9.3", - "typescript-eslint": "8.54.0" + "typescript-eslint": "8.55.0" } } From 45cd4df6e5a3d71001424c0a6b036f582bd4130d Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Wed, 11 Feb 2026 00:37:42 +0100 Subject: [PATCH 48/81] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20agent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/MachineClientsTable.tsx | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/src/components/MachineClientsTable.tsx b/src/components/MachineClientsTable.tsx index 5af44dd5..97de4113 100644 --- a/src/components/MachineClientsTable.tsx +++ b/src/components/MachineClientsTable.tsx @@ -336,21 +336,7 @@ export default function MachineClientsTable({ { accessorKey: "client", friendlyName: t("agent"), - header: ({ column }) => { - return ( - - ); - }, + header: () => {t("agent")}, cell: ({ row }) => { const originalRow = row.original; From 6d1665004b1d40f4e655c6a3b2a40bd78a387331 Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Wed, 11 Feb 2026 04:34:53 +0100 Subject: [PATCH 49/81] =?UTF-8?q?=F0=9F=8F=B7=EF=B8=8F=20fix=20type=20erro?= =?UTF-8?q?rs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../clients/user/[niceId]/general/page.tsx | 33 ++++++++++++------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/src/app/[orgId]/settings/clients/user/[niceId]/general/page.tsx b/src/app/[orgId]/settings/clients/user/[niceId]/general/page.tsx index eef93476..6dbc40b3 100644 --- a/src/app/[orgId]/settings/clients/user/[niceId]/general/page.tsx +++ b/src/app/[orgId]/settings/clients/user/[niceId]/general/page.tsx @@ -594,7 +594,8 @@ export default function GeneralPage() { {isPaidUser ? formatPostureValue( client.posture - .biometricsEnabled + .biometricsEnabled === + true ) : "-"} @@ -612,7 +613,8 @@ export default function GeneralPage() { {isPaidUser ? formatPostureValue( client.posture - .diskEncrypted + .diskEncrypted === + true ) : "-"} @@ -630,7 +632,8 @@ export default function GeneralPage() { {isPaidUser ? formatPostureValue( client.posture - .firewallEnabled + .firewallEnabled === + true ) : "-"} @@ -648,7 +651,8 @@ export default function GeneralPage() { {isPaidUser ? formatPostureValue( client.posture - .autoUpdatesEnabled + .autoUpdatesEnabled === + true ) : "-"} @@ -666,7 +670,8 @@ export default function GeneralPage() { {isPaidUser ? formatPostureValue( client.posture - .tpmAvailable + .tpmAvailable === + true ) : "-"} @@ -685,7 +690,8 @@ export default function GeneralPage() { {isPaidUser ? formatPostureValue( client.posture - .windowsAntivirusEnabled + .windowsAntivirusEnabled === + true ) : "-"} @@ -703,7 +709,8 @@ export default function GeneralPage() { {isPaidUser ? formatPostureValue( client.posture - .macosSipEnabled + .macosSipEnabled === + true ) : "-"} @@ -722,7 +729,8 @@ export default function GeneralPage() { {isPaidUser ? formatPostureValue( client.posture - .macosGatekeeperEnabled + .macosGatekeeperEnabled === + true ) : "-"} @@ -741,7 +749,8 @@ export default function GeneralPage() { {isPaidUser ? formatPostureValue( client.posture - .macosFirewallStealthMode + .macosFirewallStealthMode === + true ) : "-"} @@ -759,7 +768,8 @@ export default function GeneralPage() { {isPaidUser ? formatPostureValue( client.posture - .linuxAppArmorEnabled + .linuxAppArmorEnabled === + true ) : "-"} @@ -777,7 +787,8 @@ export default function GeneralPage() { {isPaidUser ? formatPostureValue( client.posture - .linuxSELinuxEnabled + .linuxSELinuxEnabled === + true ) : "-"} From 4e1e0cade1420fe965e9e77245bbd36b00d7daf5 Mon Sep 17 00:00:00 2001 From: Lokowitz Date: Thu, 12 Feb 2026 15:51:19 +0000 Subject: [PATCH 50/81] upgrade package --- package-lock.json | 653 +++++++++++++++------------------------------- package.json | 16 +- 2 files changed, 221 insertions(+), 448 deletions(-) diff --git a/package-lock.json b/package-lock.json index b111d659..8973f2da 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "SEE LICENSE IN LICENSE AND README.md", "dependencies": { "@asteasolutions/zod-to-openapi": "8.4.0", - "@aws-sdk/client-s3": "3.986.0", + "@aws-sdk/client-s3": "3.988.0", "@faker-js/faker": "10.3.0", "@headlessui/react": "2.2.9", "@hookform/resolvers": "5.2.2", @@ -42,7 +42,7 @@ "@simplewebauthn/browser": "13.2.2", "@simplewebauthn/server": "13.2.2", "@tailwindcss/forms": "0.5.11", - "@tanstack/react-query": "5.90.20", + "@tanstack/react-query": "5.90.21", "@tanstack/react-table": "8.21.3", "arctic": "3.7.0", "axios": "1.13.5", @@ -58,11 +58,11 @@ "drizzle-orm": "0.45.1", "express": "5.2.1", "express-rate-limit": "8.2.1", - "glob": "13.0.1", + "glob": "13.0.2", "helmet": "8.1.0", "http-errors": "2.0.1", "input-otp": "1.4.2", - "ioredis": "5.9.2", + "ioredis": "5.9.3", "jmespath": "0.16.0", "js-yaml": "4.1.1", "jsonwebtoken": "9.0.3", @@ -77,7 +77,7 @@ "nodemailer": "8.0.1", "oslo": "1.2.1", "pg": "8.18.0", - "posthog-node": "5.24.14", + "posthog-node": "5.24.15", "qrcode.react": "4.2.0", "react": "19.2.4", "react-day-picker": "9.13.2", @@ -87,7 +87,7 @@ "react-icons": "5.5.0", "recharts": "2.15.4", "reodotdev": "1.0.0", - "resend": "6.9.1", + "resend": "6.9.2", "semver": "7.7.4", "stripe": "20.3.1", "swagger-ui-express": "5.0.1", @@ -121,11 +121,11 @@ "@types/jmespath": "0.15.2", "@types/js-yaml": "4.0.9", "@types/jsonwebtoken": "9.0.10", - "@types/node": "25.2.2", + "@types/node": "25.2.3", "@types/nodemailer": "7.0.9", "@types/nprogress": "0.2.3", "@types/pg": "8.16.0", - "@types/react": "19.2.13", + "@types/react": "19.2.14", "@types/react-dom": "19.2.3", "@types/semver": "7.7.1", "@types/swagger-ui-express": "4.1.8", @@ -205,7 +205,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz", "integrity": "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==", - "license": "Apache-2.0", "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", @@ -268,7 +267,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", - "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-js": "^5.2.0", "@aws-crypto/supports-web-crypto": "^5.2.0", @@ -283,7 +281,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", - "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, @@ -295,7 +292,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", - "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" @@ -308,7 +304,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", - "license": "Apache-2.0", "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" @@ -321,7 +316,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", - "license": "Apache-2.0", "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", @@ -390,33 +384,33 @@ } }, "node_modules/@aws-sdk/client-s3": { - "version": "3.986.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.986.0.tgz", - "integrity": "sha512-IcDJ8shVVvbxgMe8+dLWcv6uhSwmX65PHTVGX81BhWAElPnp3CL8w/5uzOPRo4n4/bqIk9eskGVEIicw2o+SrA==", + "version": "3.988.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.988.0.tgz", + "integrity": "sha512-mt7AdkieJJ5hEKeCxH4sdTTd679shUjo/cUvNY0fUHgQIPZa1jRuekTXnRytRrEwdrZWJDx56n1S8ism2uX7jg==", "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.7", - "@aws-sdk/credential-provider-node": "^3.972.6", + "@aws-sdk/core": "^3.973.8", + "@aws-sdk/credential-provider-node": "^3.972.7", "@aws-sdk/middleware-bucket-endpoint": "^3.972.3", "@aws-sdk/middleware-expect-continue": "^3.972.3", - "@aws-sdk/middleware-flexible-checksums": "^3.972.5", + "@aws-sdk/middleware-flexible-checksums": "^3.972.6", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-location-constraint": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", - "@aws-sdk/middleware-sdk-s3": "^3.972.7", + "@aws-sdk/middleware-sdk-s3": "^3.972.8", "@aws-sdk/middleware-ssec": "^3.972.3", - "@aws-sdk/middleware-user-agent": "^3.972.7", + "@aws-sdk/middleware-user-agent": "^3.972.8", "@aws-sdk/region-config-resolver": "^3.972.3", - "@aws-sdk/signature-v4-multi-region": "3.986.0", + "@aws-sdk/signature-v4-multi-region": "3.988.0", "@aws-sdk/types": "^3.973.1", - "@aws-sdk/util-endpoints": "3.986.0", + "@aws-sdk/util-endpoints": "3.988.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", - "@aws-sdk/util-user-agent-node": "^3.972.5", + "@aws-sdk/util-user-agent-node": "^3.972.6", "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.22.1", + "@smithy/core": "^3.23.0", "@smithy/eventstream-serde-browser": "^4.2.8", "@smithy/eventstream-serde-config-resolver": "^4.3.8", "@smithy/eventstream-serde-node": "^4.2.8", @@ -427,25 +421,25 @@ "@smithy/invalid-dependency": "^4.2.8", "@smithy/md5-js": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.13", - "@smithy/middleware-retry": "^4.4.30", + "@smithy/middleware-endpoint": "^4.4.14", + "@smithy/middleware-retry": "^4.4.31", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.9", + "@smithy/node-http-handler": "^4.4.10", "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.11.2", + "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.29", - "@smithy/util-defaults-mode-node": "^4.2.32", + "@smithy/util-defaults-mode-browser": "^4.3.30", + "@smithy/util-defaults-mode-node": "^4.2.33", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", - "@smithy/util-stream": "^4.5.11", + "@smithy/util-stream": "^4.5.12", "@smithy/util-utf8": "^4.2.0", "@smithy/util-waiter": "^4.2.8", "tslib": "^2.6.2" @@ -454,60 +448,44 @@ "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/util-endpoints": { - "version": "3.986.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.986.0.tgz", - "integrity": "sha512-Mqi79L38qi1gCG3adlVdbNrSxvcm1IPDLiJPA3OBypY5ewxUyWbaA3DD4goG+EwET6LSFgZJcRSIh6KBNpP5pA==", - "dependencies": { - "@aws-sdk/types": "^3.973.1", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-endpoints": "^3.2.8", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, "node_modules/@aws-sdk/client-sso": { - "version": "3.985.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.985.0.tgz", - "integrity": "sha512-81J8iE8MuXhdbMfIz4sWFj64Pe41bFi/uqqmqOC5SlGv+kwoyLsyKS/rH2tW2t5buih4vTUxskRjxlqikTD4oQ==", - "license": "Apache-2.0", + "version": "3.988.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.988.0.tgz", + "integrity": "sha512-ThqQ7aF1k0Zz4yJRwegHw+T1rM3a7ZPvvEUSEdvn5Z8zTeWgJAbtqW/6ejPsMLmFOlHgNcwDQN/e69OvtEOoIQ==", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.7", + "@aws-sdk/core": "^3.973.8", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", - "@aws-sdk/middleware-user-agent": "^3.972.7", + "@aws-sdk/middleware-user-agent": "^3.972.8", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/types": "^3.973.1", - "@aws-sdk/util-endpoints": "3.985.0", + "@aws-sdk/util-endpoints": "3.988.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", - "@aws-sdk/util-user-agent-node": "^3.972.5", + "@aws-sdk/util-user-agent-node": "^3.972.6", "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.22.1", + "@smithy/core": "^3.23.0", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.13", - "@smithy/middleware-retry": "^4.4.30", + "@smithy/middleware-endpoint": "^4.4.14", + "@smithy/middleware-retry": "^4.4.31", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.9", + "@smithy/node-http-handler": "^4.4.10", "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.11.2", + "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.29", - "@smithy/util-defaults-mode-node": "^4.2.32", + "@smithy/util-defaults-mode-browser": "^4.3.30", + "@smithy/util-defaults-mode-node": "^4.2.33", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", @@ -519,19 +497,18 @@ } }, "node_modules/@aws-sdk/core": { - "version": "3.973.7", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.7.tgz", - "integrity": "sha512-wNZZQQNlJ+hzD49cKdo+PY6rsTDElO8yDImnrI69p2PLBa7QomeUKAJWYp9xnaR38nlHqWhMHZuYLCQ3oSX+xg==", - "license": "Apache-2.0", + "version": "3.973.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.8.tgz", + "integrity": "sha512-WeYJ2sfvRLbbUIrjGMUXcEHGu5SJk53jz3K9F8vFP42zWyROzPJ2NB6lMu9vWl5hnMwzwabX7pJc9Euh3JyMGw==", "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws-sdk/xml-builder": "^3.972.4", - "@smithy/core": "^3.22.1", + "@smithy/core": "^3.23.0", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", - "@smithy/smithy-client": "^4.11.2", + "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.8", @@ -546,7 +523,6 @@ "version": "3.972.0", "resolved": "https://registry.npmjs.org/@aws-sdk/crc64-nvme/-/crc64-nvme-3.972.0.tgz", "integrity": "sha512-ThlLhTqX68jvoIVv+pryOdb5coP1cX1/MaTbB9xkGDCbWbsqQcLqzPxuSoW1DCnAAIacmXCWpzUNOB9pv+xXQw==", - "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" @@ -556,12 +532,11 @@ } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.972.5", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.5.tgz", - "integrity": "sha512-LxJ9PEO4gKPXzkufvIESUysykPIdrV7+Ocb9yAhbhJLE4TiAYqbCVUE+VuKP1leGR1bBfjWjYgSV5MxprlX3mQ==", - "license": "Apache-2.0", + "version": "3.972.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.6.tgz", + "integrity": "sha512-+dYEBWgTqkQQHFUllvBL8SLyXyLKWdxLMD1LmKJRvmb0NMJuaJFG/qg78C+LE67eeGbipYcE+gJ48VlLBGHlMw==", "dependencies": { - "@aws-sdk/core": "^3.973.7", + "@aws-sdk/core": "^3.973.8", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/types": "^4.12.0", @@ -572,20 +547,19 @@ } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.7", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.7.tgz", - "integrity": "sha512-L2uOGtvp2x3bTcxFTpSM+GkwFIPd8pHfGWO1764icMbo7e5xJh0nfhx1UwkXLnwvocTNEf8A7jISZLYjUSNaTg==", - "license": "Apache-2.0", + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.8.tgz", + "integrity": "sha512-z3QkozMV8kOFisN2pgRag/f0zPDrw96mY+ejAM0xssV/+YQ2kklbylRNI/TcTQUDnGg0yPxNjyV6F2EM2zPTwg==", "dependencies": { - "@aws-sdk/core": "^3.973.7", + "@aws-sdk/core": "^3.973.8", "@aws-sdk/types": "^3.973.1", "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/node-http-handler": "^4.4.9", + "@smithy/node-http-handler": "^4.4.10", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.11.2", + "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", - "@smithy/util-stream": "^4.5.11", + "@smithy/util-stream": "^4.5.12", "tslib": "^2.6.2" }, "engines": { @@ -593,19 +567,18 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.972.5", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.5.tgz", - "integrity": "sha512-SdDTYE6jkARzOeL7+kudMIM4DaFnP5dZVeatzw849k4bSXDdErDS188bgeNzc/RA2WGrlEpsqHUKP6G7sVXhZg==", - "license": "Apache-2.0", + "version": "3.972.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.6.tgz", + "integrity": "sha512-6tkIYFv3sZH1XsjQq+veOmx8XWRnyqTZ5zx/sMtdu/xFRIzrJM1Y2wAXeCJL1rhYSB7uJSZ1PgALI2WVTj78ow==", "dependencies": { - "@aws-sdk/core": "^3.973.7", - "@aws-sdk/credential-provider-env": "^3.972.5", - "@aws-sdk/credential-provider-http": "^3.972.7", - "@aws-sdk/credential-provider-login": "^3.972.5", - "@aws-sdk/credential-provider-process": "^3.972.5", - "@aws-sdk/credential-provider-sso": "^3.972.5", - "@aws-sdk/credential-provider-web-identity": "^3.972.5", - "@aws-sdk/nested-clients": "3.985.0", + "@aws-sdk/core": "^3.973.8", + "@aws-sdk/credential-provider-env": "^3.972.6", + "@aws-sdk/credential-provider-http": "^3.972.8", + "@aws-sdk/credential-provider-login": "^3.972.6", + "@aws-sdk/credential-provider-process": "^3.972.6", + "@aws-sdk/credential-provider-sso": "^3.972.6", + "@aws-sdk/credential-provider-web-identity": "^3.972.6", + "@aws-sdk/nested-clients": "3.988.0", "@aws-sdk/types": "^3.973.1", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/property-provider": "^4.2.8", @@ -618,13 +591,12 @@ } }, "node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.5", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.5.tgz", - "integrity": "sha512-uYq1ILyTSI6ZDCMY5+vUsRM0SOCVI7kaW4wBrehVVkhAxC6y+e9rvGtnoZqCOWL1gKjTMouvsf4Ilhc5NCg1Aw==", - "license": "Apache-2.0", + "version": "3.972.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.6.tgz", + "integrity": "sha512-LXsoBoaTSGHdRCQXlWSA0CHHh05KWncb592h9ElklnPus++8kYn1Ic6acBR4LKFQ0RjjMVgwe5ypUpmTSUOjPA==", "dependencies": { - "@aws-sdk/core": "^3.973.7", - "@aws-sdk/nested-clients": "3.985.0", + "@aws-sdk/core": "^3.973.8", + "@aws-sdk/nested-clients": "3.988.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", @@ -637,17 +609,16 @@ } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.6", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.6.tgz", - "integrity": "sha512-DZ3CnAAtSVtVz+G+ogqecaErMLgzph4JH5nYbHoBMgBkwTUV+SUcjsjOJwdBJTHu3Dm6l5LBYekZoU2nDqQk2A==", - "license": "Apache-2.0", + "version": "3.972.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.7.tgz", + "integrity": "sha512-PuJ1IkISG7ZDpBFYpGotaay6dYtmriBYuHJ/Oko4VHxh8YN5vfoWnMNYFEWuzOfyLmP7o9kDVW0BlYIpb3skvw==", "dependencies": { - "@aws-sdk/credential-provider-env": "^3.972.5", - "@aws-sdk/credential-provider-http": "^3.972.7", - "@aws-sdk/credential-provider-ini": "^3.972.5", - "@aws-sdk/credential-provider-process": "^3.972.5", - "@aws-sdk/credential-provider-sso": "^3.972.5", - "@aws-sdk/credential-provider-web-identity": "^3.972.5", + "@aws-sdk/credential-provider-env": "^3.972.6", + "@aws-sdk/credential-provider-http": "^3.972.8", + "@aws-sdk/credential-provider-ini": "^3.972.6", + "@aws-sdk/credential-provider-process": "^3.972.6", + "@aws-sdk/credential-provider-sso": "^3.972.6", + "@aws-sdk/credential-provider-web-identity": "^3.972.6", "@aws-sdk/types": "^3.973.1", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/property-provider": "^4.2.8", @@ -660,12 +631,11 @@ } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.972.5", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.5.tgz", - "integrity": "sha512-HDKF3mVbLnuqGg6dMnzBf1VUOywE12/N286msI9YaK9mEIzdsGCtLTvrDhe3Up0R9/hGFbB+9l21/TwF5L1C6g==", - "license": "Apache-2.0", + "version": "3.972.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.6.tgz", + "integrity": "sha512-Yf34cjIZJHVnD92jnVYy3tNjM+Q4WJtffLK2Ehn0nKpZfqd1m7SI0ra22Lym4C53ED76oZENVSS2wimoXJtChQ==", "dependencies": { - "@aws-sdk/core": "^3.973.7", + "@aws-sdk/core": "^3.973.8", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", @@ -677,14 +647,13 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.972.5", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.5.tgz", - "integrity": "sha512-8urj3AoeNeQisjMmMBhFeiY2gxt6/7wQQbEGun0YV/OaOOiXrIudTIEYF8ZfD+NQI6X1FY5AkRsx6O/CaGiybA==", - "license": "Apache-2.0", + "version": "3.972.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.6.tgz", + "integrity": "sha512-2+5UVwUYdD4BBOkLpKJ11MQ8wQeyJGDVMDRH5eWOULAh9d6HJq07R69M/mNNMC9NTjr3mB1T0KGDn4qyQh5jzg==", "dependencies": { - "@aws-sdk/client-sso": "3.985.0", - "@aws-sdk/core": "^3.973.7", - "@aws-sdk/token-providers": "3.985.0", + "@aws-sdk/client-sso": "3.988.0", + "@aws-sdk/core": "^3.973.8", + "@aws-sdk/token-providers": "3.988.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", @@ -696,13 +665,12 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.972.5", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.5.tgz", - "integrity": "sha512-OK3cULuJl6c+RcDZfPpaK5o3deTOnKZbxm7pzhFNGA3fI2hF9yDih17fGRazJzGGWaDVlR9ejZrpDef4DJCEsw==", - "license": "Apache-2.0", + "version": "3.972.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.6.tgz", + "integrity": "sha512-pdJzwKtlDxBnvZ04pWMqttijmkUIlwOsS0GcxCjzEVyUMpARysl0S0ks74+gs2Pdev3Ujz+BTAjOc1tQgAxGqA==", "dependencies": { - "@aws-sdk/core": "^3.973.7", - "@aws-sdk/nested-clients": "3.985.0", + "@aws-sdk/core": "^3.973.8", + "@aws-sdk/nested-clients": "3.988.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", @@ -747,15 +715,14 @@ } }, "node_modules/@aws-sdk/middleware-flexible-checksums": { - "version": "3.972.5", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.972.5.tgz", - "integrity": "sha512-SF/1MYWx67OyCrLA4icIpWUfCkdlOi8Y1KecQ9xYxkL10GMjVdPTGPnYhAg0dw5U43Y9PVUWhAV2ezOaG+0BLg==", - "license": "Apache-2.0", + "version": "3.972.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.972.6.tgz", + "integrity": "sha512-g5DadWO58IgQKuq+uLL3pLohOwLiA67gB49xj8694BW+LpHLNu/tjCqwLfIaWvZyABbv0LXeNiiTuTnjdgkZWw==", "dependencies": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", "@aws-crypto/util": "5.2.0", - "@aws-sdk/core": "^3.973.7", + "@aws-sdk/core": "^3.973.8", "@aws-sdk/crc64-nvme": "3.972.0", "@aws-sdk/types": "^3.973.1", "@smithy/is-array-buffer": "^4.2.0", @@ -763,7 +730,7 @@ "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "@smithy/util-middleware": "^4.2.8", - "@smithy/util-stream": "^4.5.11", + "@smithy/util-stream": "^4.5.12", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, @@ -775,7 +742,6 @@ "version": "3.972.3", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.3.tgz", "integrity": "sha512-aknPTb2M+G3s+0qLCx4Li/qGZH8IIYjugHMv15JTYMe6mgZO8VBpYgeGYsNMGCqCZOcWzuf900jFBG5bopfzmA==", - "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/protocol-http": "^5.3.8", @@ -804,7 +770,6 @@ "version": "3.972.3", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.3.tgz", "integrity": "sha512-Ftg09xNNRqaz9QNzlfdQWfpqMCJbsQdnZVJP55jfhbKi1+FTWxGuvfPoBhDHIovqWKjqbuiew3HuhxbJ0+OjgA==", - "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", @@ -818,7 +783,6 @@ "version": "3.972.3", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.3.tgz", "integrity": "sha512-PY57QhzNuXHnwbJgbWYTrqIDHYSeOlhfYERTAuc16LKZpTZRJUjzBFokp9hF7u1fuGeE3D70ERXzdbMBOqQz7Q==", - "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws/lambda-invoke-store": "^0.2.2", @@ -831,22 +795,22 @@ } }, "node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.972.7", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.7.tgz", - "integrity": "sha512-VtZ7tMIw18VzjG+I6D6rh2eLkJfTtByiFoCIauGDtTTPBEUMQUiGaJ/zZrPlCY6BsvLLeFKz3+E5mntgiOWmIg==", + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.8.tgz", + "integrity": "sha512-/yJdahpN/q3Dc88qXBTQVZfnXryLnxfCoP4hGClbKjuF0VCMxrz3il7sj0GhIkEQt5OM5+lA88XrvbjjuwSxIg==", "dependencies": { - "@aws-sdk/core": "^3.973.7", + "@aws-sdk/core": "^3.973.8", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-arn-parser": "^3.972.2", - "@smithy/core": "^3.22.1", + "@smithy/core": "^3.23.0", "@smithy/node-config-provider": "^4.3.8", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", - "@smithy/smithy-client": "^4.11.2", + "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-middleware": "^4.2.8", - "@smithy/util-stream": "^4.5.11", + "@smithy/util-stream": "^4.5.12", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, @@ -869,15 +833,14 @@ } }, "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.972.7", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.7.tgz", - "integrity": "sha512-HUD+geASjXSCyL/DHPQc/Ua7JhldTcIglVAoCV8kiVm99IaFSlAbTvEnyhZwdE6bdFyTL+uIaWLaCFSRsglZBQ==", - "license": "Apache-2.0", + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.8.tgz", + "integrity": "sha512-3PGL+Kvh1PhB0EeJeqNqOWQgipdqFheO4OUKc6aYiFwEpM5t9AyE5hjjxZ5X6iSj8JiduWFZLPwASzF6wQRgFg==", "dependencies": { - "@aws-sdk/core": "^3.973.7", + "@aws-sdk/core": "^3.973.8", "@aws-sdk/types": "^3.973.1", - "@aws-sdk/util-endpoints": "3.985.0", - "@smithy/core": "^3.22.1", + "@aws-sdk/util-endpoints": "3.988.0", + "@smithy/core": "^3.23.0", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" @@ -887,44 +850,43 @@ } }, "node_modules/@aws-sdk/nested-clients": { - "version": "3.985.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.985.0.tgz", - "integrity": "sha512-TsWwKzb/2WHafAY0CE7uXgLj0FmnkBTgfioG9HO+7z/zCPcl1+YU+i7dW4o0y+aFxFgxTMG+ExBQpqT/k2ao8g==", - "license": "Apache-2.0", + "version": "3.988.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.988.0.tgz", + "integrity": "sha512-OgYV9k1oBCQ6dOM+wWAMNNehXA8L4iwr7ydFV+JDHyuuu0Ko7tDXnLEtEmeQGYRcAFU3MGasmlBkMB8vf4POrg==", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.7", + "@aws-sdk/core": "^3.973.8", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", - "@aws-sdk/middleware-user-agent": "^3.972.7", + "@aws-sdk/middleware-user-agent": "^3.972.8", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/types": "^3.973.1", - "@aws-sdk/util-endpoints": "3.985.0", + "@aws-sdk/util-endpoints": "3.988.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", - "@aws-sdk/util-user-agent-node": "^3.972.5", + "@aws-sdk/util-user-agent-node": "^3.972.6", "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.22.1", + "@smithy/core": "^3.23.0", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.13", - "@smithy/middleware-retry": "^4.4.30", + "@smithy/middleware-endpoint": "^4.4.14", + "@smithy/middleware-retry": "^4.4.31", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.9", + "@smithy/node-http-handler": "^4.4.10", "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.11.2", + "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.29", - "@smithy/util-defaults-mode-node": "^4.2.32", + "@smithy/util-defaults-mode-browser": "^4.3.30", + "@smithy/util-defaults-mode-node": "^4.2.33", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", @@ -939,7 +901,6 @@ "version": "3.972.3", "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.3.tgz", "integrity": "sha512-v4J8qYAWfOMcZ4MJUyatntOicTzEMaU7j3OpkRCGGFSL2NgXQ5VbxauIyORA+pxdKZ0qQG2tCQjQjZDlXEC3Ow==", - "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/config-resolver": "^4.4.6", @@ -952,11 +913,11 @@ } }, "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.986.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.986.0.tgz", - "integrity": "sha512-Upw+rw7wCH93E6QWxqpAqJLrUmJYVUAWrk4tCOBnkeuwzGERZvJFL5UQ6TAJFj9T18Ih+vNFaACh8J5aP4oTBw==", + "version": "3.988.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.988.0.tgz", + "integrity": "sha512-SXwhbe2v0Jno7QLIBmZWAL2eVzGmXkfLLy0WkM6ZJVhE0SFUcnymDwMUA1oMDUvyArzvKBiU8khQ2ImheCKOHQ==", "dependencies": { - "@aws-sdk/middleware-sdk-s3": "^3.972.7", + "@aws-sdk/middleware-sdk-s3": "^3.972.8", "@aws-sdk/types": "^3.973.1", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", @@ -968,13 +929,12 @@ } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.985.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.985.0.tgz", - "integrity": "sha512-+hwpHZyEq8k+9JL2PkE60V93v2kNhUIv7STFt+EAez1UJsJOQDhc5LpzEX66pNjclI5OTwBROs/DhJjC/BtMjQ==", - "license": "Apache-2.0", + "version": "3.988.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.988.0.tgz", + "integrity": "sha512-xvXVlRVKHnF2h6fgWBm64aPP5J+58aJyGfRrQa/uFh8a9mcK68mLfJOYq+ZSxQy/UN3McafJ2ILAy7IWzT9kRw==", "dependencies": { - "@aws-sdk/core": "^3.973.7", - "@aws-sdk/nested-clients": "3.985.0", + "@aws-sdk/core": "^3.973.8", + "@aws-sdk/nested-clients": "3.988.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", @@ -1011,10 +971,9 @@ } }, "node_modules/@aws-sdk/util-endpoints": { - "version": "3.985.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.985.0.tgz", - "integrity": "sha512-vth7UfGSUR3ljvaq8V4Rc62FsM7GUTH/myxPWkaEgOrprz1/Pc72EgTXxj+cPPPDAfHFIpjhkB7T7Td0RJx+BA==", - "license": "Apache-2.0", + "version": "3.988.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.988.0.tgz", + "integrity": "sha512-HuXu4boeUWU0DQiLslbgdvuQ4ZMCo4Lsk97w8BIUokql2o9MvjE5dwqI5pzGt0K7afO1FybjidUQVTMLuZNTOA==", "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", @@ -1042,7 +1001,6 @@ "version": "3.972.3", "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.3.tgz", "integrity": "sha512-JurOwkRUcXD/5MTDBcqdyQ9eVedtAsZgw5rBwktsPTN7QtPiS2Ld1jkJepNgYoCufz1Wcut9iup7GJDoIHp8Fw==", - "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", @@ -1051,12 +1009,11 @@ } }, "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.972.5", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.972.5.tgz", - "integrity": "sha512-GsUDF+rXyxDZkkJxUsDxnA67FG+kc5W1dnloCFLl6fWzceevsCYzJpASBzT+BPjwUgREE6FngfJYYYMQUY5fZQ==", - "license": "Apache-2.0", + "version": "3.972.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.972.6.tgz", + "integrity": "sha512-966xH8TPqkqOXP7EwnEThcKKz0SNP9kVJBKd9M8bNXE4GSqVouMKKnFBwYnzbWVKuLXubzX5seokcX4a0JLJIA==", "dependencies": { - "@aws-sdk/middleware-user-agent": "^3.972.7", + "@aws-sdk/middleware-user-agent": "^3.972.8", "@aws-sdk/types": "^3.973.1", "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", @@ -1078,7 +1035,6 @@ "version": "3.972.4", "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.4.tgz", "integrity": "sha512-0zJ05ANfYqI6+rGqj8samZBFod0dPPousBjLEqg8WdxSgbMAkRgLyn81lP215Do0rFJ/17LIXwr7q0yK24mP6Q==", - "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.12.0", "fast-xml-parser": "5.3.4", @@ -1092,7 +1048,6 @@ "version": "0.2.3", "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.3.tgz", "integrity": "sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw==", - "license": "Apache-2.0", "engines": { "node": ">=18.0.0" } @@ -2859,7 +2814,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -2882,7 +2836,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -2905,7 +2858,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2922,7 +2874,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2939,7 +2890,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2956,7 +2906,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2973,7 +2922,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2990,7 +2938,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -3007,7 +2954,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -3024,7 +2970,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -3041,7 +2986,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -3058,7 +3002,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -3081,7 +3024,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -3104,7 +3046,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -3127,7 +3068,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -3150,7 +3090,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -3173,7 +3112,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -3196,7 +3134,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -3219,7 +3156,6 @@ "cpu": [ "wasm32" ], - "dev": true, "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", "optional": true, "dependencies": { @@ -3239,7 +3175,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ @@ -3259,7 +3194,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ @@ -3279,7 +3213,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ @@ -4701,9 +4634,9 @@ } }, "node_modules/@posthog/core": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/@posthog/core/-/core-1.21.0.tgz", - "integrity": "sha512-0a2JUIX1vhduP2El/6/J8s5AeYAurIoufQGFgMiGnJE5ajd63o9LFocu2vFYYBnIOmy75y4ADNeW8zSl1keEQQ==", + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/@posthog/core/-/core-1.22.0.tgz", + "integrity": "sha512-WkmOnq95aAOu6yk6r5LWr5cfXsQdpVbWDCwOxQwxSne8YV6GuZET1ziO5toSQXgrgbdcjrSz2/GopAfiL6iiAA==", "dependencies": { "cross-spawn": "^7.0.6" } @@ -8135,7 +8068,6 @@ "version": "4.4.6", "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.6.tgz", "integrity": "sha512-qJpzYC64kaj3S0fueiu3kXm8xPrR3PcXDPEgnaNMRn0EjNSZFoFjvbUp0YUDsRhN1CB90EnHJtbxWKevnH99UQ==", - "license": "Apache-2.0", "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", @@ -8149,10 +8081,9 @@ } }, "node_modules/@smithy/core": { - "version": "3.22.1", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.22.1.tgz", - "integrity": "sha512-x3ie6Crr58MWrm4viHqqy2Du2rHYZjwu8BekasrQx4ca+Y24dzVAwq3yErdqIbc2G3I0kLQA13PQ+/rde+u65g==", - "license": "Apache-2.0", + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.23.0.tgz", + "integrity": "sha512-Yq4UPVoQICM9zHnByLmG8632t2M0+yap4T7ANVw482J0W7HW0pOuxwVmeOwzJqX2Q89fkXz0Vybz55Wj2Xzrsg==", "dependencies": { "@smithy/middleware-serde": "^4.2.9", "@smithy/protocol-http": "^5.3.8", @@ -8160,7 +8091,7 @@ "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-middleware": "^4.2.8", - "@smithy/util-stream": "^4.5.11", + "@smithy/util-stream": "^4.5.12", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" @@ -8173,7 +8104,6 @@ "version": "4.2.8", "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.8.tgz", "integrity": "sha512-FNT0xHS1c/CPN8upqbMFP83+ul5YgdisfCfkZ86Jh2NSmnqw/AJ6x5pEogVCTVvSm7j9MopRU89bmDelxuDMYw==", - "license": "Apache-2.0", "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", @@ -8259,7 +8189,6 @@ "version": "5.3.9", "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.9.tgz", "integrity": "sha512-I4UhmcTYXBrct03rwzQX1Y/iqQlzVQaPxWjCjula++5EmWq9YGBrx6bbGqluGc1f0XEfhSkiY4jhLgbsJUMKRA==", - "license": "Apache-2.0", "dependencies": { "@smithy/protocol-http": "^5.3.8", "@smithy/querystring-builder": "^4.2.8", @@ -8290,7 +8219,6 @@ "version": "4.2.8", "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.8.tgz", "integrity": "sha512-7ZIlPbmaDGxVoxErDZnuFG18WekhbA/g2/i97wGj+wUBeS6pcUeAym8u4BXh/75RXWhgIJhyC11hBzig6MljwA==", - "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.12.0", "@smithy/util-buffer-from": "^4.2.0", @@ -8319,7 +8247,6 @@ "version": "4.2.8", "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.8.tgz", "integrity": "sha512-N9iozRybwAQ2dn9Fot9kI6/w9vos2oTXLhtK7ovGqwZjlOcxu6XhPlpLpC+INsxktqHinn5gS2DXDjDF2kG5sQ==", - "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" @@ -8358,7 +8285,6 @@ "version": "4.2.8", "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.8.tgz", "integrity": "sha512-RO0jeoaYAB1qBRhfVyq0pMgBoUK34YEJxVxyjOWYZiOKOq2yMZ4MnVXMZCUDenpozHue207+9P5ilTV1zeda0A==", - "license": "Apache-2.0", "dependencies": { "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", @@ -8369,12 +8295,11 @@ } }, "node_modules/@smithy/middleware-endpoint": { - "version": "4.4.13", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.13.tgz", - "integrity": "sha512-x6vn0PjYmGdNuKh/juUJJewZh7MoQ46jYaJ2mvekF4EesMuFfrl4LaW/k97Zjf8PTCPQmPgMvwewg7eNoH9n5w==", - "license": "Apache-2.0", + "version": "4.4.14", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.14.tgz", + "integrity": "sha512-FUFNE5KVeaY6U/GL0nzAAHkaCHzXLZcY1EhtQnsAqhD8Du13oPKtMB9/0WK4/LK6a/T5OZ24wPoSShff5iI6Ag==", "dependencies": { - "@smithy/core": "^3.22.1", + "@smithy/core": "^3.23.0", "@smithy/middleware-serde": "^4.2.9", "@smithy/node-config-provider": "^4.3.8", "@smithy/shared-ini-file-loader": "^4.4.3", @@ -8388,15 +8313,14 @@ } }, "node_modules/@smithy/middleware-retry": { - "version": "4.4.30", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.30.tgz", - "integrity": "sha512-CBGyFvN0f8hlnqKH/jckRDz78Snrp345+PVk8Ux7pnkUCW97Iinse59lY78hBt04h1GZ6hjBN94BRwZy1xC8Bg==", - "license": "Apache-2.0", + "version": "4.4.31", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.31.tgz", + "integrity": "sha512-RXBzLpMkIrxBPe4C8OmEOHvS8aH9RUuCOH++Acb5jZDEblxDjyg6un72X9IcbrGTJoiUwmI7hLypNfuDACypbg==", "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/protocol-http": "^5.3.8", "@smithy/service-error-classification": "^4.2.8", - "@smithy/smithy-client": "^4.11.2", + "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", @@ -8411,7 +8335,6 @@ "version": "4.2.9", "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.9.tgz", "integrity": "sha512-eMNiej0u/snzDvlqRGSN3Vl0ESn3838+nKyVfF2FKNXFbi4SERYT6PR392D39iczngbqqGG0Jl1DlCnp7tBbXQ==", - "license": "Apache-2.0", "dependencies": { "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", @@ -8425,7 +8348,6 @@ "version": "4.2.8", "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.8.tgz", "integrity": "sha512-w6LCfOviTYQjBctOKSwy6A8FIkQy7ICvglrZFl6Bw4FmcQ1Z420fUtIhxaUZZshRe0VCq4kvDiPiXrPZAe8oRA==", - "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" @@ -8450,10 +8372,9 @@ } }, "node_modules/@smithy/node-http-handler": { - "version": "4.4.9", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.9.tgz", - "integrity": "sha512-KX5Wml5mF+luxm1szW4QDz32e3NObgJ4Fyw+irhph4I/2geXwUy4jkIMUs5ZPGflRBeR6BUkC2wqIab4Llgm3w==", - "license": "Apache-2.0", + "version": "4.4.10", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.10.tgz", + "integrity": "sha512-u4YeUwOWRZaHbWaebvrs3UhwQwj+2VNmcVCwXcYTvPIuVyM7Ex1ftAj+fdbG/P4AkBwLq/+SKn+ydOI4ZJE9PA==", "dependencies": { "@smithy/abort-controller": "^4.2.8", "@smithy/protocol-http": "^5.3.8", @@ -8495,7 +8416,6 @@ "version": "4.2.8", "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.8.tgz", "integrity": "sha512-Xr83r31+DrE8CP3MqPgMJl+pQlLLmOfiEUnoyAlGzzJIrEsbKsPy1hqH0qySaQm4oWrCBlUqRt+idEgunKB+iw==", - "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.12.0", "@smithy/util-uri-escape": "^4.2.0", @@ -8509,7 +8429,6 @@ "version": "4.2.8", "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.8.tgz", "integrity": "sha512-vUurovluVy50CUlazOiXkPq40KGvGWSdmusa3130MwrR1UNnNgKAlj58wlOe61XSHRpUfIIh6cE0zZ8mzKaDPA==", - "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" @@ -8522,7 +8441,6 @@ "version": "4.2.8", "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.8.tgz", "integrity": "sha512-mZ5xddodpJhEt3RkCjbmUQuXUOaPNTkbMGR0bcS8FE0bJDLMZlhmpgrvPNCYglVw5rsYTpSnv19womw9WWXKQQ==", - "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.12.0" }, @@ -8547,7 +8465,6 @@ "version": "5.3.8", "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.8.tgz", "integrity": "sha512-6A4vdGj7qKNRF16UIcO8HhHjKW27thsxYci+5r/uVRkdcBEkOEiY8OMPuydLX4QHSrJqGHPJzPRwwVTqbLZJhg==", - "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "@smithy/protocol-http": "^5.3.8", @@ -8563,17 +8480,16 @@ } }, "node_modules/@smithy/smithy-client": { - "version": "4.11.2", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.11.2.tgz", - "integrity": "sha512-SCkGmFak/xC1n7hKRsUr6wOnBTJ3L22Qd4e8H1fQIuKTAjntwgU8lrdMe7uHdiT2mJAOWA/60qaW9tiMu69n1A==", - "license": "Apache-2.0", + "version": "4.11.3", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.11.3.tgz", + "integrity": "sha512-Q7kY5sDau8OoE6Y9zJoRGgje8P4/UY0WzH8R2ok0PDh+iJ+ZnEKowhjEqYafVcubkbYxQVaqwm3iufktzhprGg==", "dependencies": { - "@smithy/core": "^3.22.1", - "@smithy/middleware-endpoint": "^4.4.13", + "@smithy/core": "^3.23.0", + "@smithy/middleware-endpoint": "^4.4.14", "@smithy/middleware-stack": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", - "@smithy/util-stream": "^4.5.11", + "@smithy/util-stream": "^4.5.12", "tslib": "^2.6.2" }, "engines": { @@ -8596,7 +8512,6 @@ "version": "4.2.8", "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.8.tgz", "integrity": "sha512-NQho9U68TGMEU639YkXnVMV3GEFFULmmaWdlu1E9qzyIePOHsoSnagTGSDv1Zi8DCNN6btxOSdgmy5E/hsZwhA==", - "license": "Apache-2.0", "dependencies": { "@smithy/querystring-parser": "^4.2.8", "@smithy/types": "^4.12.0", @@ -8624,7 +8539,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz", "integrity": "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==", - "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, @@ -8636,7 +8550,6 @@ "version": "4.2.1", "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.1.tgz", "integrity": "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==", - "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, @@ -8670,13 +8583,12 @@ } }, "node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.3.29", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.29.tgz", - "integrity": "sha512-nIGy3DNRmOjaYaaKcQDzmWsro9uxlaqUOhZDHQed9MW/GmkBZPtnU70Pu1+GT9IBmUXwRdDuiyaeiy9Xtpn3+Q==", - "license": "Apache-2.0", + "version": "4.3.30", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.30.tgz", + "integrity": "sha512-cMni0uVU27zxOiU8TuC8pQLC1pYeZ/xEMxvchSK/ILwleRd1ugobOcIRr5vXtcRqKd4aBLWlpeBoDPJJ91LQng==", "dependencies": { "@smithy/property-provider": "^4.2.8", - "@smithy/smithy-client": "^4.11.2", + "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, @@ -8685,16 +8597,15 @@ } }, "node_modules/@smithy/util-defaults-mode-node": { - "version": "4.2.32", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.32.tgz", - "integrity": "sha512-7dtFff6pu5fsjqrVve0YMhrnzJtccCWDacNKOkiZjJ++fmjGExmmSu341x+WU6Oc1IccL7lDuaUj7SfrHpWc5Q==", - "license": "Apache-2.0", + "version": "4.2.33", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.33.tgz", + "integrity": "sha512-LEb2aq5F4oZUSzWBG7S53d4UytZSkOEJPXcBq/xbG2/TmK9EW5naUZ8lKu1BEyWMzdHIzEVN16M3k8oxDq+DJA==", "dependencies": { "@smithy/config-resolver": "^4.4.6", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", - "@smithy/smithy-client": "^4.11.2", + "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, @@ -8706,7 +8617,6 @@ "version": "3.2.8", "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.8.tgz", "integrity": "sha512-8JaVTn3pBDkhZgHQ8R0epwWt+BqPSLCjdjXXusK1onwJlRuN69fbvSK66aIKKO7SwVFM6x2J2ox5X8pOaWcUEw==", - "license": "Apache-2.0", "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", @@ -8732,7 +8642,6 @@ "version": "4.2.8", "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.8.tgz", "integrity": "sha512-PMqfeJxLcNPMDgvPbbLl/2Vpin+luxqTGPpW3NAQVLbRrFRzTa4rNAASYeIGjRV9Ytuhzny39SpyU04EQreF+A==", - "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" @@ -8745,7 +8654,6 @@ "version": "4.2.8", "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.8.tgz", "integrity": "sha512-CfJqwvoRY0kTGe5AkQokpURNCT1u/MkRzMTASWMPPo2hNSnKtF1D45dQl3DE2LKLr4m+PW9mCeBMJr5mCAVThg==", - "license": "Apache-2.0", "dependencies": { "@smithy/service-error-classification": "^4.2.8", "@smithy/types": "^4.12.0", @@ -8756,13 +8664,12 @@ } }, "node_modules/@smithy/util-stream": { - "version": "4.5.11", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.11.tgz", - "integrity": "sha512-lKmZ0S/3Qj2OF5H1+VzvDLb6kRxGzZHq6f3rAsoSu5cTLGsn3v3VQBA8czkNNXlLjoFEtVu3OQT2jEeOtOE2CA==", - "license": "Apache-2.0", + "version": "4.5.12", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.12.tgz", + "integrity": "sha512-D8tgkrmhAX/UNeCZbqbEO3uqyghUnEmmoO9YEvRuwxjlkKKUE7FOgCJnqpTlQPe9MApdWPky58mNQQHbnCzoNg==", "dependencies": { "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/node-http-handler": "^4.4.9", + "@smithy/node-http-handler": "^4.4.10", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-buffer-from": "^4.2.0", @@ -8778,7 +8685,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz", "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==", - "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, @@ -8817,7 +8723,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.0.tgz", "integrity": "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==", - "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, @@ -9441,10 +9346,9 @@ } }, "node_modules/@tanstack/react-query": { - "version": "5.90.20", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.20.tgz", - "integrity": "sha512-vXBxa+qeyveVO7OA0jX1z+DeyCA4JKnThKv411jd5SORpBKgkcVnYKCiBgECvADvniBX7tobwBmg01qq9JmMJw==", - "license": "MIT", + "version": "5.90.21", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.21.tgz", + "integrity": "sha512-0Lu6y5t+tvlTJMTO7oh5NSpJfpg/5D41LlThfepTixPYkJ0sE2Jj0m0f6yYqujBwIXlId87e234+MxG3D3g7kg==", "dependencies": { "@tanstack/query-core": "5.90.20" }, @@ -9980,9 +9884,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.2.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.2.tgz", - "integrity": "sha512-BkmoP5/FhRYek5izySdkOneRyXYN35I860MFAGupTdebyE66uZaR+bXLHq8k4DirE5DwQi3NuhvRU1jqTVwUrQ==", + "version": "25.2.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.3.tgz", + "integrity": "sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==", "devOptional": true, "dependencies": { "undici-types": "~7.16.0" @@ -10032,11 +9936,10 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "19.2.13", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.13.tgz", - "integrity": "sha512-KkiJeU6VbYbUOp5ITMIc7kBfqlYkKA5KhEHVrGMmUUMt7NeaZg65ojdPk+FtNrBAOXNVM5QM72jnADjM+XVRAQ==", + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "devOptional": true, - "license": "MIT", "dependencies": { "csstype": "^3.2.2" } @@ -10664,17 +10567,6 @@ "win32" ] }, - "node_modules/@zone-eu/mailsplit": { - "version": "5.4.8", - "resolved": "https://registry.npmjs.org/@zone-eu/mailsplit/-/mailsplit-5.4.8.tgz", - "integrity": "sha512-eEyACj4JZ7sjzRvy26QhLgKEMWwQbsw1+QZnlLX+/gihcNH07lVPOcnwf5U6UAL7gkc//J3jVd76o/WS+taUiA==", - "license": "(MIT OR EUPL-1.1+)", - "dependencies": { - "libbase64": "1.3.0", - "libmime": "5.3.7", - "libqp": "2.1.1" - } - }, "node_modules/accepts": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", @@ -11276,10 +11168,9 @@ } }, "node_modules/bowser": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.13.1.tgz", - "integrity": "sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw==", - "license": "MIT" + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", + "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==" }, "node_modules/brace-expansion": { "version": "1.1.12", @@ -13460,15 +13351,6 @@ "node": ">= 0.8" } }, - "node_modules/encoding-japanese": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/encoding-japanese/-/encoding-japanese-2.2.0.tgz", - "integrity": "sha512-EuJWwlHPZ1LbADuKTClvHtwbaFn4rOD+dRAbWysqEOXRc2Uui0hJInNJrsdH0c+OhJA4nrCBdSkW4DD5YxAo6A==", - "license": "MIT", - "engines": { - "node": ">=8.10.0" - } - }, "node_modules/end-of-stream": { "version": "1.4.5", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", @@ -14601,7 +14483,6 @@ "url": "https://github.com/sponsors/NaturalIntelligence" } ], - "license": "MIT", "dependencies": { "strnum": "^2.1.0" }, @@ -15075,10 +14956,9 @@ "license": "MIT" }, "node_modules/glob": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.1.tgz", - "integrity": "sha512-B7U/vJpE3DkJ5WXTgTpTRN63uV42DseiXXKMwG14LQBXmsdeIoHAPbU/MEo6II0k5ED74uc2ZGTC6MwHFQhF6w==", - "license": "BlueOak-1.0.0", + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.2.tgz", + "integrity": "sha512-035InabNu/c1lW0tzPhAgapKctblppqsKKG9ZaNzbr+gXwWMjXoiyGSyB9sArzrjG7jY+zntRq5ZSUYemrnWVQ==", "dependencies": { "minimatch": "^10.1.2", "minipass": "^7.1.2", @@ -15277,15 +15157,6 @@ "node": ">= 0.4" } }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "license": "MIT", - "bin": { - "he": "bin/he" - } - }, "node_modules/helmet": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz", @@ -15520,10 +15391,9 @@ } }, "node_modules/ioredis": { - "version": "5.9.2", - "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.9.2.tgz", - "integrity": "sha512-tAAg/72/VxOUW7RQSX1pIxJVucYKcjFjfvj60L57jrZpYCHC3XN0WCQ3sNYL4Gmvv+7GPvTAjc+KSdeNuE8oWQ==", - "license": "MIT", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.9.3.tgz", + "integrity": "sha512-VI5tMCdeoxZWU5vjHWsiE/Su76JGhBvWF1MJnV9ZtGltHk9BmD48oDq8Tj8haZ85aceXZMxLNDQZRVo5QKNgXA==", "dependencies": { "@ioredis/commands": "1.5.0", "cluster-key-slot": "^1.1.0", @@ -16271,30 +16141,6 @@ "node": ">= 0.8.0" } }, - "node_modules/libbase64": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/libbase64/-/libbase64-1.3.0.tgz", - "integrity": "sha512-GgOXd0Eo6phYgh0DJtjQ2tO8dc0IVINtZJeARPeiIJqge+HdsWSuaDTe8ztQ7j/cONByDZ3zeB325AHiv5O0dg==", - "license": "MIT" - }, - "node_modules/libmime": { - "version": "5.3.7", - "resolved": "https://registry.npmjs.org/libmime/-/libmime-5.3.7.tgz", - "integrity": "sha512-FlDb3Wtha8P01kTL3P9M+ZDNDWPKPmKHWaU/cG/lg5pfuAwdflVpZE+wm9m7pKmC5ww6s+zTxBKS1p6yl3KpSw==", - "license": "MIT", - "dependencies": { - "encoding-japanese": "2.2.0", - "iconv-lite": "0.6.3", - "libbase64": "1.3.0", - "libqp": "2.1.1" - } - }, - "node_modules/libqp": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/libqp/-/libqp-2.1.1.tgz", - "integrity": "sha512-0Wd+GPz1O134cP62YU2GTOPNA7Qgl09XwCqM5zpBv87ERCXdfDtyKXvV7c9U22yWJh44QZqBocFnXN11K96qow==", - "license": "MIT" - }, "node_modules/lightningcss": { "version": "1.30.2", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", @@ -16556,15 +16402,6 @@ "url": "https://opencollective.com/parcel" } }, - "node_modules/linkify-it": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", - "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", - "license": "MIT", - "dependencies": { - "uc.micro": "^2.0.0" - } - }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -16706,49 +16543,6 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, - "node_modules/mailparser": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/mailparser/-/mailparser-3.9.1.tgz", - "integrity": "sha512-6vHZcco3fWsDMkf4Vz9iAfxvwrKNGbHx0dV1RKVphQ/zaNY34Buc7D37LSa09jeSeybWzYcTPjhiZFxzVRJedA==", - "license": "MIT", - "dependencies": { - "@zone-eu/mailsplit": "5.4.8", - "encoding-japanese": "2.2.0", - "he": "1.2.0", - "html-to-text": "9.0.5", - "iconv-lite": "0.7.0", - "libmime": "5.3.7", - "linkify-it": "5.0.0", - "nodemailer": "7.0.11", - "punycode.js": "2.3.1", - "tlds": "1.261.0" - } - }, - "node_modules/mailparser/node_modules/iconv-lite": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", - "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/mailparser/node_modules/nodemailer": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.11.tgz", - "integrity": "sha512-gnXhNRE0FNhD7wPSCGhdNh46Hs6nm+uTyg+Kq0cZukNQiYdnCsoQjodNP9BQVG9XrcK/v6/MgpAPBUFyzh9pvw==", - "license": "MIT-0", - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/marked": { "version": "15.0.12", "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz", @@ -18177,6 +17971,11 @@ "node": ">= 0.4" } }, + "node_modules/postal-mime": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/postal-mime/-/postal-mime-2.7.3.tgz", + "integrity": "sha512-MjhXadAJaWgYzevi46+3kLak8y6gbg0ku14O1gO/LNOuay8dO+1PtcSGvAdgDR0DoIsSaiIA8y/Ddw6MnrO0Tw==" + }, "node_modules/postcss": { "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", @@ -18246,11 +18045,11 @@ } }, "node_modules/posthog-node": { - "version": "5.24.14", - "resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-5.24.14.tgz", - "integrity": "sha512-KbOQAZ66V9t4Abh/x62pL/2n504HlxQEavFZjpcyIpVwQEPmmafsoLU5ueL47m3i6m6r619Z76m4uyoxVfGqsA==", + "version": "5.24.15", + "resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-5.24.15.tgz", + "integrity": "sha512-0QnWVOZAPwEAlp+r3r0jIGfk2IaNYM/2YnEJJhBMJZXs4LpHcTu7mX42l+e95o9xX87YpVuZU0kOkmtQUxgnOA==", "dependencies": { - "@posthog/core": "1.21.0" + "@posthog/core": "1.22.0" }, "engines": { "node": "^20.20.0 || >=22.22.0" @@ -18380,15 +18179,6 @@ "node": ">=6" } }, - "node_modules/punycode.js": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", - "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/pvtsutils": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz", @@ -19656,12 +19446,11 @@ } }, "node_modules/resend": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/resend/-/resend-6.9.1.tgz", - "integrity": "sha512-jFY3qPP2cith1npRXvS7PVdnhbR1CcuzHg65ty5Elv55GKiXhe+nItXuzzoOlKeYJez1iJAo2+8f6ae8sCj0iA==", - "license": "MIT", + "version": "6.9.2", + "resolved": "https://registry.npmjs.org/resend/-/resend-6.9.2.tgz", + "integrity": "sha512-uIM6CQ08tS+hTCRuKBFbOBvHIGaEhqZe8s4FOgqsVXSbQLAhmNWpmUhG3UAtRnmcwTWFUqnHa/+Vux8YGPyDBA==", "dependencies": { - "mailparser": "3.9.1", + "postal-mime": "2.7.3", "svix": "1.84.1" }, "engines": { @@ -20761,8 +20550,7 @@ "type": "github", "url": "https://github.com/sponsors/NaturalIntelligence" } - ], - "license": "MIT" + ] }, "node_modules/stubborn-fs": { "version": "2.0.0", @@ -21002,15 +20790,6 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/tlds": { - "version": "1.261.0", - "resolved": "https://registry.npmjs.org/tlds/-/tlds-1.261.0.tgz", - "integrity": "sha512-QXqwfEl9ddlGBaRFXIvNKK6OhipSiLXuRuLJX5DErz0o0Q0rYxulWLdFryTkV5PkdZct5iMInwYEGe/eR++1AA==", - "license": "MIT", - "bin": { - "tlds": "bin.js" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -21418,12 +21197,6 @@ "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/uc.micro": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", - "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", - "license": "MIT" - }, "node_modules/uint8array-extras": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", diff --git a/package.json b/package.json index dbacd4f8..cb786444 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ }, "dependencies": { "@asteasolutions/zod-to-openapi": "8.4.0", - "@aws-sdk/client-s3": "3.986.0", + "@aws-sdk/client-s3": "3.988.0", "@faker-js/faker": "10.3.0", "@headlessui/react": "2.2.9", "@hookform/resolvers": "5.2.2", @@ -68,7 +68,7 @@ "@simplewebauthn/browser": "13.2.2", "@simplewebauthn/server": "13.2.2", "@tailwindcss/forms": "0.5.11", - "@tanstack/react-query": "5.90.20", + "@tanstack/react-query": "5.90.21", "@tanstack/react-table": "8.21.3", "arctic": "3.7.0", "axios": "1.13.5", @@ -84,11 +84,11 @@ "drizzle-orm": "0.45.1", "express": "5.2.1", "express-rate-limit": "8.2.1", - "glob": "13.0.1", + "glob": "13.0.2", "helmet": "8.1.0", "http-errors": "2.0.1", "input-otp": "1.4.2", - "ioredis": "5.9.2", + "ioredis": "5.9.3", "jmespath": "0.16.0", "js-yaml": "4.1.1", "jsonwebtoken": "9.0.3", @@ -103,7 +103,7 @@ "nodemailer": "8.0.1", "oslo": "1.2.1", "pg": "8.18.0", - "posthog-node": "5.24.14", + "posthog-node": "5.24.15", "qrcode.react": "4.2.0", "react": "19.2.4", "react-day-picker": "9.13.2", @@ -113,7 +113,7 @@ "react-icons": "5.5.0", "recharts": "2.15.4", "reodotdev": "1.0.0", - "resend": "6.9.1", + "resend": "6.9.2", "semver": "7.7.4", "stripe": "20.3.1", "swagger-ui-express": "5.0.1", @@ -147,11 +147,11 @@ "@types/jmespath": "0.15.2", "@types/js-yaml": "4.0.9", "@types/jsonwebtoken": "9.0.10", - "@types/node": "25.2.2", + "@types/node": "25.2.3", "@types/nodemailer": "7.0.9", "@types/nprogress": "0.2.3", "@types/pg": "8.16.0", - "@types/react": "19.2.13", + "@types/react": "19.2.14", "@types/react-dom": "19.2.3", "@types/semver": "7.7.1", "@types/swagger-ui-express": "4.1.8", From 5f3657fd56b0829f03145114174a1bbbe90399b7 Mon Sep 17 00:00:00 2001 From: Lokowitz Date: Fri, 13 Feb 2026 06:15:26 +0000 Subject: [PATCH 51/81] update packages --- package-lock.json | 485 ++++++++++++++-------------------------------- package.json | 4 +- 2 files changed, 150 insertions(+), 339 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8973f2da..764d475f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "SEE LICENSE IN LICENSE AND README.md", "dependencies": { "@asteasolutions/zod-to-openapi": "8.4.0", - "@aws-sdk/client-s3": "3.988.0", + "@aws-sdk/client-s3": "3.989.0", "@faker-js/faker": "10.3.0", "@headlessui/react": "2.2.9", "@hookform/resolvers": "5.2.2", @@ -58,7 +58,7 @@ "drizzle-orm": "0.45.1", "express": "5.2.1", "express-rate-limit": "8.2.1", - "glob": "13.0.2", + "glob": "13.0.3", "helmet": "8.1.0", "http-errors": "2.0.1", "input-otp": "1.4.2", @@ -384,31 +384,31 @@ } }, "node_modules/@aws-sdk/client-s3": { - "version": "3.988.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.988.0.tgz", - "integrity": "sha512-mt7AdkieJJ5hEKeCxH4sdTTd679shUjo/cUvNY0fUHgQIPZa1jRuekTXnRytRrEwdrZWJDx56n1S8ism2uX7jg==", + "version": "3.989.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.989.0.tgz", + "integrity": "sha512-ccz2miIetWAgrJYmKCpSnRjF8jew7DPstl54nufhfPMtM1MLxD2z55eSk1eJj3Umhu4CioNN1aY1ILT7fwlSiw==", "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.8", - "@aws-sdk/credential-provider-node": "^3.972.7", + "@aws-sdk/core": "^3.973.9", + "@aws-sdk/credential-provider-node": "^3.972.8", "@aws-sdk/middleware-bucket-endpoint": "^3.972.3", "@aws-sdk/middleware-expect-continue": "^3.972.3", - "@aws-sdk/middleware-flexible-checksums": "^3.972.6", + "@aws-sdk/middleware-flexible-checksums": "^3.972.7", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-location-constraint": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", - "@aws-sdk/middleware-sdk-s3": "^3.972.8", + "@aws-sdk/middleware-sdk-s3": "^3.972.9", "@aws-sdk/middleware-ssec": "^3.972.3", - "@aws-sdk/middleware-user-agent": "^3.972.8", + "@aws-sdk/middleware-user-agent": "^3.972.9", "@aws-sdk/region-config-resolver": "^3.972.3", - "@aws-sdk/signature-v4-multi-region": "3.988.0", + "@aws-sdk/signature-v4-multi-region": "3.989.0", "@aws-sdk/types": "^3.973.1", - "@aws-sdk/util-endpoints": "3.988.0", + "@aws-sdk/util-endpoints": "3.989.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", - "@aws-sdk/util-user-agent-node": "^3.972.6", + "@aws-sdk/util-user-agent-node": "^3.972.7", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.23.0", "@smithy/eventstream-serde-browser": "^4.2.8", @@ -449,22 +449,22 @@ } }, "node_modules/@aws-sdk/client-sso": { - "version": "3.988.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.988.0.tgz", - "integrity": "sha512-ThqQ7aF1k0Zz4yJRwegHw+T1rM3a7ZPvvEUSEdvn5Z8zTeWgJAbtqW/6ejPsMLmFOlHgNcwDQN/e69OvtEOoIQ==", + "version": "3.989.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.989.0.tgz", + "integrity": "sha512-3sC+J1ru5VFXLgt9KZmXto0M7mnV5RkS6FNGwRMK3XrojSjHso9DLOWjbnXhbNv4motH8vu53L1HK2VC1+Nj5w==", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.8", + "@aws-sdk/core": "^3.973.9", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", - "@aws-sdk/middleware-user-agent": "^3.972.8", + "@aws-sdk/middleware-user-agent": "^3.972.9", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/types": "^3.973.1", - "@aws-sdk/util-endpoints": "3.988.0", + "@aws-sdk/util-endpoints": "3.989.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", - "@aws-sdk/util-user-agent-node": "^3.972.6", + "@aws-sdk/util-user-agent-node": "^3.972.7", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.23.0", "@smithy/fetch-http-handler": "^5.3.9", @@ -497,9 +497,9 @@ } }, "node_modules/@aws-sdk/core": { - "version": "3.973.8", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.8.tgz", - "integrity": "sha512-WeYJ2sfvRLbbUIrjGMUXcEHGu5SJk53jz3K9F8vFP42zWyROzPJ2NB6lMu9vWl5hnMwzwabX7pJc9Euh3JyMGw==", + "version": "3.973.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.9.tgz", + "integrity": "sha512-cyUOfJSizn8da7XrBEFBf4UMI4A6JQNX6ZFcKtYmh/CrwfzsDcabv3k/z0bNwQ3pX5aeq5sg/8Bs/ASiL0bJaA==", "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws-sdk/xml-builder": "^3.972.4", @@ -532,11 +532,11 @@ } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.972.6", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.6.tgz", - "integrity": "sha512-+dYEBWgTqkQQHFUllvBL8SLyXyLKWdxLMD1LmKJRvmb0NMJuaJFG/qg78C+LE67eeGbipYcE+gJ48VlLBGHlMw==", + "version": "3.972.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.7.tgz", + "integrity": "sha512-r8kBtglvLjGxBT87l6Lqkh9fL8yJJ6O4CYQPjKlj3AkCuL4/4784x3rxxXWw9LTKXOo114VB6mjxAuy5pI7XIg==", "dependencies": { - "@aws-sdk/core": "^3.973.8", + "@aws-sdk/core": "^3.973.9", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/types": "^4.12.0", @@ -547,11 +547,11 @@ } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.8", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.8.tgz", - "integrity": "sha512-z3QkozMV8kOFisN2pgRag/f0zPDrw96mY+ejAM0xssV/+YQ2kklbylRNI/TcTQUDnGg0yPxNjyV6F2EM2zPTwg==", + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.9.tgz", + "integrity": "sha512-40caFblEg/TPrp9EpvyMxp4xlJ5TuTI+A8H6g8FhHn2hfH2PObFAPLF9d5AljK/G69E1YtTklkuQeAwPlV3w8Q==", "dependencies": { - "@aws-sdk/core": "^3.973.8", + "@aws-sdk/core": "^3.973.9", "@aws-sdk/types": "^3.973.1", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/node-http-handler": "^4.4.10", @@ -567,18 +567,18 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.972.6", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.6.tgz", - "integrity": "sha512-6tkIYFv3sZH1XsjQq+veOmx8XWRnyqTZ5zx/sMtdu/xFRIzrJM1Y2wAXeCJL1rhYSB7uJSZ1PgALI2WVTj78ow==", + "version": "3.972.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.7.tgz", + "integrity": "sha512-zeYKrMwM5bCkHFho/x3+1OL0vcZQ0OhTR7k35tLq74+GP5ieV3juHXTZfa2LVE0Bg75cHIIerpX0gomVOhzo/w==", "dependencies": { - "@aws-sdk/core": "^3.973.8", - "@aws-sdk/credential-provider-env": "^3.972.6", - "@aws-sdk/credential-provider-http": "^3.972.8", - "@aws-sdk/credential-provider-login": "^3.972.6", - "@aws-sdk/credential-provider-process": "^3.972.6", - "@aws-sdk/credential-provider-sso": "^3.972.6", - "@aws-sdk/credential-provider-web-identity": "^3.972.6", - "@aws-sdk/nested-clients": "3.988.0", + "@aws-sdk/core": "^3.973.9", + "@aws-sdk/credential-provider-env": "^3.972.7", + "@aws-sdk/credential-provider-http": "^3.972.9", + "@aws-sdk/credential-provider-login": "^3.972.7", + "@aws-sdk/credential-provider-process": "^3.972.7", + "@aws-sdk/credential-provider-sso": "^3.972.7", + "@aws-sdk/credential-provider-web-identity": "^3.972.7", + "@aws-sdk/nested-clients": "3.989.0", "@aws-sdk/types": "^3.973.1", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/property-provider": "^4.2.8", @@ -591,12 +591,12 @@ } }, "node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.6", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.6.tgz", - "integrity": "sha512-LXsoBoaTSGHdRCQXlWSA0CHHh05KWncb592h9ElklnPus++8kYn1Ic6acBR4LKFQ0RjjMVgwe5ypUpmTSUOjPA==", + "version": "3.972.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.7.tgz", + "integrity": "sha512-Q103cLU6OjAllYjX7+V+PKQw654jjvZUkD+lbUUiFbqut6gR5zwl1DrelvJPM5hnzIty7BCaxaRB3KMuz3M/ug==", "dependencies": { - "@aws-sdk/core": "^3.973.8", - "@aws-sdk/nested-clients": "3.988.0", + "@aws-sdk/core": "^3.973.9", + "@aws-sdk/nested-clients": "3.989.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", @@ -609,16 +609,16 @@ } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.7", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.7.tgz", - "integrity": "sha512-PuJ1IkISG7ZDpBFYpGotaay6dYtmriBYuHJ/Oko4VHxh8YN5vfoWnMNYFEWuzOfyLmP7o9kDVW0BlYIpb3skvw==", + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.8.tgz", + "integrity": "sha512-AaDVOT7iNJyLjc3j91VlucPZ4J8Bw+eu9sllRDugJqhHWYyR3Iyp2huBUW8A3+DfHoh70sxGkY92cThAicSzlQ==", "dependencies": { - "@aws-sdk/credential-provider-env": "^3.972.6", - "@aws-sdk/credential-provider-http": "^3.972.8", - "@aws-sdk/credential-provider-ini": "^3.972.6", - "@aws-sdk/credential-provider-process": "^3.972.6", - "@aws-sdk/credential-provider-sso": "^3.972.6", - "@aws-sdk/credential-provider-web-identity": "^3.972.6", + "@aws-sdk/credential-provider-env": "^3.972.7", + "@aws-sdk/credential-provider-http": "^3.972.9", + "@aws-sdk/credential-provider-ini": "^3.972.7", + "@aws-sdk/credential-provider-process": "^3.972.7", + "@aws-sdk/credential-provider-sso": "^3.972.7", + "@aws-sdk/credential-provider-web-identity": "^3.972.7", "@aws-sdk/types": "^3.973.1", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/property-provider": "^4.2.8", @@ -631,11 +631,11 @@ } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.972.6", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.6.tgz", - "integrity": "sha512-Yf34cjIZJHVnD92jnVYy3tNjM+Q4WJtffLK2Ehn0nKpZfqd1m7SI0ra22Lym4C53ED76oZENVSS2wimoXJtChQ==", + "version": "3.972.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.7.tgz", + "integrity": "sha512-hxMo1V3ujWWrQSONxQJAElnjredkRpB6p8SDjnvRq70IwYY38R/CZSys0IbhRPxdgWZ5j12yDRk2OXhxw4Gj3g==", "dependencies": { - "@aws-sdk/core": "^3.973.8", + "@aws-sdk/core": "^3.973.9", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", @@ -647,13 +647,13 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.972.6", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.6.tgz", - "integrity": "sha512-2+5UVwUYdD4BBOkLpKJ11MQ8wQeyJGDVMDRH5eWOULAh9d6HJq07R69M/mNNMC9NTjr3mB1T0KGDn4qyQh5jzg==", + "version": "3.972.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.7.tgz", + "integrity": "sha512-ZGKBOHEj8Ap15jhG2XMncQmKLTqA++2DVU2eZfLu3T/pkwDyhCp5eZv5c/acFxbZcA/6mtxke+vzO/n+aeHs4A==", "dependencies": { - "@aws-sdk/client-sso": "3.988.0", - "@aws-sdk/core": "^3.973.8", - "@aws-sdk/token-providers": "3.988.0", + "@aws-sdk/client-sso": "3.989.0", + "@aws-sdk/core": "^3.973.9", + "@aws-sdk/token-providers": "3.989.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", @@ -665,12 +665,12 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.972.6", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.6.tgz", - "integrity": "sha512-pdJzwKtlDxBnvZ04pWMqttijmkUIlwOsS0GcxCjzEVyUMpARysl0S0ks74+gs2Pdev3Ujz+BTAjOc1tQgAxGqA==", + "version": "3.972.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.7.tgz", + "integrity": "sha512-AbYupBIoSJoVMlbMqBhNvPhqj+CdGtzW7Uk4ZIMBm2br18pc3rkG1VaKVFV85H87QCvLHEnni1idJjaX1wOmIw==", "dependencies": { - "@aws-sdk/core": "^3.973.8", - "@aws-sdk/nested-clients": "3.988.0", + "@aws-sdk/core": "^3.973.9", + "@aws-sdk/nested-clients": "3.989.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", @@ -715,14 +715,14 @@ } }, "node_modules/@aws-sdk/middleware-flexible-checksums": { - "version": "3.972.6", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.972.6.tgz", - "integrity": "sha512-g5DadWO58IgQKuq+uLL3pLohOwLiA67gB49xj8694BW+LpHLNu/tjCqwLfIaWvZyABbv0LXeNiiTuTnjdgkZWw==", + "version": "3.972.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.972.7.tgz", + "integrity": "sha512-YU/5rpz8k2mwFGi2M0px9ChOQZY7Bbow5knB2WLRVPqDM/cG8T5zj55UaWS1qcaFpE7vCX9a9/kvYBlKGcD+KA==", "dependencies": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", "@aws-crypto/util": "5.2.0", - "@aws-sdk/core": "^3.973.8", + "@aws-sdk/core": "^3.973.9", "@aws-sdk/crc64-nvme": "3.972.0", "@aws-sdk/types": "^3.973.1", "@smithy/is-array-buffer": "^4.2.0", @@ -795,11 +795,11 @@ } }, "node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.972.8", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.8.tgz", - "integrity": "sha512-/yJdahpN/q3Dc88qXBTQVZfnXryLnxfCoP4hGClbKjuF0VCMxrz3il7sj0GhIkEQt5OM5+lA88XrvbjjuwSxIg==", + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.9.tgz", + "integrity": "sha512-F4Ak2HM7te/o3izFTqg/jUTBLjavpaJ5iynKM6aLMwNddXbwAZQ1VbIG8RFUHBo7fBHj2eeN2FNLtIFT4ejWYQ==", "dependencies": { - "@aws-sdk/core": "^3.973.8", + "@aws-sdk/core": "^3.973.9", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-arn-parser": "^3.972.2", "@smithy/core": "^3.23.0", @@ -833,13 +833,13 @@ } }, "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.972.8", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.8.tgz", - "integrity": "sha512-3PGL+Kvh1PhB0EeJeqNqOWQgipdqFheO4OUKc6aYiFwEpM5t9AyE5hjjxZ5X6iSj8JiduWFZLPwASzF6wQRgFg==", + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.9.tgz", + "integrity": "sha512-1g1B7yf7KzessB0mKNiV9gAHEwbM662xgU+VE4LxyGe6kVGZ8LqYsngjhE+Stna09CJ7Pxkjr6Uq1OtbGwJJJg==", "dependencies": { - "@aws-sdk/core": "^3.973.8", + "@aws-sdk/core": "^3.973.9", "@aws-sdk/types": "^3.973.1", - "@aws-sdk/util-endpoints": "3.988.0", + "@aws-sdk/util-endpoints": "3.989.0", "@smithy/core": "^3.23.0", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", @@ -850,22 +850,22 @@ } }, "node_modules/@aws-sdk/nested-clients": { - "version": "3.988.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.988.0.tgz", - "integrity": "sha512-OgYV9k1oBCQ6dOM+wWAMNNehXA8L4iwr7ydFV+JDHyuuu0Ko7tDXnLEtEmeQGYRcAFU3MGasmlBkMB8vf4POrg==", + "version": "3.989.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.989.0.tgz", + "integrity": "sha512-Dbk2HMPU3mb6RrSRzgf0WCaWSbgtZG258maCpuN2/ONcAQNpOTw99V5fU5CA1qVK6Vkm4Fwj2cnOnw7wbGVlOw==", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.8", + "@aws-sdk/core": "^3.973.9", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", - "@aws-sdk/middleware-user-agent": "^3.972.8", + "@aws-sdk/middleware-user-agent": "^3.972.9", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/types": "^3.973.1", - "@aws-sdk/util-endpoints": "3.988.0", + "@aws-sdk/util-endpoints": "3.989.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", - "@aws-sdk/util-user-agent-node": "^3.972.6", + "@aws-sdk/util-user-agent-node": "^3.972.7", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.23.0", "@smithy/fetch-http-handler": "^5.3.9", @@ -913,11 +913,11 @@ } }, "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.988.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.988.0.tgz", - "integrity": "sha512-SXwhbe2v0Jno7QLIBmZWAL2eVzGmXkfLLy0WkM6ZJVhE0SFUcnymDwMUA1oMDUvyArzvKBiU8khQ2ImheCKOHQ==", + "version": "3.989.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.989.0.tgz", + "integrity": "sha512-rVhR/BUZdnru7tLlxWD+uzoKB1LAs2L0pcoh6rYgIYuCtQflnsC6Ud0SpfqIsOapBSBKXdoW73IITFf+XFMdCQ==", "dependencies": { - "@aws-sdk/middleware-sdk-s3": "^3.972.8", + "@aws-sdk/middleware-sdk-s3": "^3.972.9", "@aws-sdk/types": "^3.973.1", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", @@ -929,12 +929,12 @@ } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.988.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.988.0.tgz", - "integrity": "sha512-xvXVlRVKHnF2h6fgWBm64aPP5J+58aJyGfRrQa/uFh8a9mcK68mLfJOYq+ZSxQy/UN3McafJ2ILAy7IWzT9kRw==", + "version": "3.989.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.989.0.tgz", + "integrity": "sha512-OdBByMv+OjOZoekrk4THPFpLuND5aIQbDHCGh3n2rvifAbm31+6e0OLhxSeCF1UMPm+nKq12bXYYEoCIx5SQBg==", "dependencies": { - "@aws-sdk/core": "^3.973.8", - "@aws-sdk/nested-clients": "3.988.0", + "@aws-sdk/core": "^3.973.9", + "@aws-sdk/nested-clients": "3.989.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", @@ -971,9 +971,9 @@ } }, "node_modules/@aws-sdk/util-endpoints": { - "version": "3.988.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.988.0.tgz", - "integrity": "sha512-HuXu4boeUWU0DQiLslbgdvuQ4ZMCo4Lsk97w8BIUokql2o9MvjE5dwqI5pzGt0K7afO1FybjidUQVTMLuZNTOA==", + "version": "3.989.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.989.0.tgz", + "integrity": "sha512-eKmAOeQM4Qusq0jtcbZPiNWky8XaojByKC/n+THbJ8vJf7t4ys8LlcZ4PrBSHZISe9cC484mQsPVOQh6iySjqw==", "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", @@ -1009,11 +1009,11 @@ } }, "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.972.6", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.972.6.tgz", - "integrity": "sha512-966xH8TPqkqOXP7EwnEThcKKz0SNP9kVJBKd9M8bNXE4GSqVouMKKnFBwYnzbWVKuLXubzX5seokcX4a0JLJIA==", + "version": "3.972.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.972.7.tgz", + "integrity": "sha512-oyhv+FjrgHjP+F16cmsrJzNP4qaRJzkV1n9Lvv4uyh3kLqo3rIe9NSBSBa35f2TedczfG2dD+kaQhHBB47D6Og==", "dependencies": { - "@aws-sdk/middleware-user-agent": "^3.972.8", + "@aws-sdk/middleware-user-agent": "^3.972.9", "@aws-sdk/types": "^3.973.1", "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", @@ -3235,6 +3235,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "dev": true, "license": "MIT", "engines": { "node": "20 || >=22" @@ -3244,6 +3245,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz", "integrity": "sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==", + "dev": true, "license": "MIT", "dependencies": { "@isaacs/balanced-match": "^4.0.1" @@ -3253,21 +3255,11 @@ } }, "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz", + "integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==", "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@jridgewell/gen-mapping": { @@ -13282,13 +13274,6 @@ "node": ">= 0.4" } }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, - "license": "MIT" - }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", @@ -14956,11 +14941,11 @@ "license": "MIT" }, "node_modules/glob": { - "version": "13.0.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.2.tgz", - "integrity": "sha512-035InabNu/c1lW0tzPhAgapKctblppqsKKG9ZaNzbr+gXwWMjXoiyGSyB9sArzrjG7jY+zntRq5ZSUYemrnWVQ==", + "version": "13.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.3.tgz", + "integrity": "sha512-/g3B0mC+4x724v1TgtBlBtt2hPi/EWptsIAmXUx9Z2rvBYleQcsrmaOzd5LyL50jf/Soi83ZDJmw2+XqvH/EeA==", "dependencies": { - "minimatch": "^10.1.2", + "minimatch": "^10.2.0", "minipass": "^7.1.2", "path-scurry": "^2.0.0" }, @@ -14984,13 +14969,34 @@ "node": ">=10.13.0" } }, - "node_modules/glob/node_modules/minimatch": { - "version": "10.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.2.tgz", - "integrity": "sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw==", - "license": "BlueOak-1.0.0", + "node_modules/glob/node_modules/balanced-match": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.2.tgz", + "integrity": "sha512-x0K50QvKQ97fdEz2kPehIerj+YTeptKF9hyYkKf6egnwmMWAkADiO0QCzSp0R5xN8FTZgYaBfSaue46Ej62nMg==", "dependencies": { - "@isaacs/brace-expansion": "^5.0.1" + "jackspeak": "^4.2.3" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.2.tgz", + "integrity": "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.0.tgz", + "integrity": "sha512-ugkC31VaVg9cF0DFVoADH12k6061zNZkZON+aX8AWsR9GhPcErkcMBceb6znR8wLERM2AkkOxy2nWRLpT9Jq5w==", + "dependencies": { + "brace-expansion": "^5.0.2" }, "engines": { "node": "20 || >=22" @@ -15614,16 +15620,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/is-generator-function": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", @@ -15907,13 +15903,11 @@ } }, "node_modules/jackspeak": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", - "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", - "dev": true, - "license": "BlueOak-1.0.0", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.3.tgz", + "integrity": "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==", "dependencies": { - "@isaacs/cliui": "^8.0.2" + "@isaacs/cliui": "^9.0.0" }, "engines": { "node": "20 || >=22" @@ -20275,70 +20269,6 @@ "safe-buffer": "~5.2.0" } }, - "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/string-width-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/string.prototype.includes": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", @@ -20467,30 +20397,6 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -21639,101 +21545,6 @@ "node": ">=0.10.0" } }, - "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi-cjs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/package.json b/package.json index 43df6c23..a9ff9a92 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ }, "dependencies": { "@asteasolutions/zod-to-openapi": "8.4.0", - "@aws-sdk/client-s3": "3.988.0", + "@aws-sdk/client-s3": "3.989.0", "@faker-js/faker": "10.3.0", "@headlessui/react": "2.2.9", "@hookform/resolvers": "5.2.2", @@ -81,7 +81,7 @@ "drizzle-orm": "0.45.1", "express": "5.2.1", "express-rate-limit": "8.2.1", - "glob": "13.0.2", + "glob": "13.0.3", "helmet": "8.1.0", "http-errors": "2.0.1", "input-otp": "1.4.2", From 1f8e89772d9249e49aac2c229ddcc20d61402115 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Fri, 13 Feb 2026 15:46:13 -0800 Subject: [PATCH 52/81] disable global idp routes if idp mode is org --- server/routers/idp/createIdpOrgPolicy.ts | 9 +++++++++ server/routers/idp/createOidcIdp.ts | 11 +++++++++++ server/routers/idp/updateIdpOrgPolicy.ts | 9 +++++++++ server/routers/idp/updateOidcIdp.ts | 9 +++++++++ 4 files changed, 38 insertions(+) diff --git a/server/routers/idp/createIdpOrgPolicy.ts b/server/routers/idp/createIdpOrgPolicy.ts index b9a0098b..dc7af537 100644 --- a/server/routers/idp/createIdpOrgPolicy.ts +++ b/server/routers/idp/createIdpOrgPolicy.ts @@ -70,6 +70,15 @@ export async function createIdpOrgPolicy( const { idpId, orgId } = parsedParams.data; const { roleMapping, orgMapping } = parsedBody.data; + if (process.env.IDENTITY_PROVIDER_MODE === "org") { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + "Global IdP creation is not allowed in the current identity provider mode. Set app.identity_provider_mode to 'global' in the private configuration to enable this feature." + ) + ); + } + const [existing] = await db .select() .from(idp) diff --git a/server/routers/idp/createOidcIdp.ts b/server/routers/idp/createOidcIdp.ts index 15728362..03626bfd 100644 --- a/server/routers/idp/createOidcIdp.ts +++ b/server/routers/idp/createOidcIdp.ts @@ -80,6 +80,17 @@ export async function createOidcIdp( tags } = parsedBody.data; + if ( + process.env.IDENTITY_PROVIDER_MODE === "org" + ) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + "Global IdP creation is not allowed in the current identity provider mode. Set app.identity_provider_mode to 'global' in the private configuration to enable this feature." + ) + ); + } + const key = config.getRawConfig().server.secret!; const encryptedSecret = encrypt(clientSecret, key); diff --git a/server/routers/idp/updateIdpOrgPolicy.ts b/server/routers/idp/updateIdpOrgPolicy.ts index 6432faf6..ea08de42 100644 --- a/server/routers/idp/updateIdpOrgPolicy.ts +++ b/server/routers/idp/updateIdpOrgPolicy.ts @@ -69,6 +69,15 @@ export async function updateIdpOrgPolicy( const { idpId, orgId } = parsedParams.data; const { roleMapping, orgMapping } = parsedBody.data; + if (process.env.IDENTITY_PROVIDER_MODE === "org") { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + "Global IdP creation is not allowed in the current identity provider mode. Set app.identity_provider_mode to 'global' in the private configuration to enable this feature." + ) + ); + } + // Check if IDP and policy exist const [existing] = await db .select() diff --git a/server/routers/idp/updateOidcIdp.ts b/server/routers/idp/updateOidcIdp.ts index 622d3d49..82aed75c 100644 --- a/server/routers/idp/updateOidcIdp.ts +++ b/server/routers/idp/updateOidcIdp.ts @@ -99,6 +99,15 @@ export async function updateOidcIdp( tags } = parsedBody.data; + if (process.env.IDENTITY_PROVIDER_MODE === "org") { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + "Global IdP creation is not allowed in the current identity provider mode. Set app.identity_provider_mode to 'global' in the private configuration to enable this feature." + ) + ); + } + // Check if IDP exists and is of type OIDC const [existingIdp] = await db .select() From aba586e60577bfdf2a16753b300c02cf9d6fa0e5 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Fri, 13 Feb 2026 17:35:54 -0800 Subject: [PATCH 53/81] change translation --- messages/en-US.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/en-US.json b/messages/en-US.json index cc388253..d31f8b1b 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -1917,7 +1917,7 @@ "authPageBrandingDeleteConfirm": "Confirm Delete Branding", "brandingLogoURL": "Logo URL", "brandingLogoURLOrPath": "Logo URL or Path", - "brandingLogoPathDescription": "Enter a URL (https://...) or a local path (/logo.png) from the public/ directory on your Pangolin installation.", + "brandingLogoPathDescription": "Enter a URL or a local path.", "brandingLogoURLDescription": "Enter a publicly accessible URL to your logo image.", "brandingPrimaryColor": "Primary Color", "brandingLogoWidth": "Width (px)", From 1fbcad8787b282c450c69698559ce3930e947485 Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Sat, 14 Feb 2026 04:06:11 +0100 Subject: [PATCH 54/81] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/db/pg/schema/schema.ts | 26 ++++++++++++-------------- server/db/sqlite/schema/schema.ts | 9 +-------- 2 files changed, 13 insertions(+), 22 deletions(-) diff --git a/server/db/pg/schema/schema.ts b/server/db/pg/schema/schema.ts index cb7eef2d..6afd463e 100644 --- a/server/db/pg/schema/schema.ts +++ b/server/db/pg/schema/schema.ts @@ -1,18 +1,16 @@ -import { - pgTable, - serial, - varchar, - boolean, - integer, - bigint, - real, - text, - index, - uniqueIndex -} from "drizzle-orm/pg-core"; -import { InferSelectModel } from "drizzle-orm"; import { randomUUID } from "crypto"; -import { alias } from "yargs"; +import { InferSelectModel } from "drizzle-orm"; +import { + bigint, + boolean, + index, + integer, + pgTable, + real, + serial, + text, + varchar +} from "drizzle-orm/pg-core"; export const domains = pgTable("domains", { domainId: varchar("domainId").primaryKey(), diff --git a/server/db/sqlite/schema/schema.ts b/server/db/sqlite/schema/schema.ts index 1b9b164c..7335f666 100644 --- a/server/db/sqlite/schema/schema.ts +++ b/server/db/sqlite/schema/schema.ts @@ -1,13 +1,6 @@ import { randomUUID } from "crypto"; import { InferSelectModel } from "drizzle-orm"; -import { - sqliteTable, - text, - integer, - index, - uniqueIndex -} from "drizzle-orm/sqlite-core"; -import { no } from "zod/v4/locales"; +import { index, integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; export const domains = sqliteTable("domains", { domainId: text("domainId").primaryKey(), From 761a5f1d4ce64b8cab33531dbeeac4cf35f82a26 Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Sat, 14 Feb 2026 04:11:27 +0100 Subject: [PATCH 55/81] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20use=20`like`=20&=20`?= =?UTF-8?q?LOWER(column)`=20for=20searching=20with=20query?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/routers/client/listClients.ts | 11 ++++- server/routers/client/listUserDevices.ts | 12 +++-- server/routers/resource/listResources.ts | 48 ++++++++++--------- server/routers/site/listSites.ts | 47 +++++++++--------- .../siteResource/listAllSiteResourcesByOrg.ts | 40 +++++++++++----- 5 files changed, 96 insertions(+), 62 deletions(-) diff --git a/server/routers/client/listClients.ts b/server/routers/client/listClients.ts index 9ba7c684..b7eec8e4 100644 --- a/server/routers/client/listClients.ts +++ b/server/routers/client/listClients.ts @@ -20,9 +20,9 @@ import { asc, desc, eq, - ilike, inArray, isNull, + like, or, sql, type SQL @@ -305,7 +305,14 @@ export async function listClients( } if (query) { - conditions.push(or(ilike(clients.name, "%" + query + "%"))); + conditions.push( + or( + like( + sql`LOWER(${clients.name})`, + "%" + query.toLowerCase() + "%" + ) + ) + ); } const baseQuery = queryClientsBase().where(and(...conditions)); diff --git a/server/routers/client/listUserDevices.ts b/server/routers/client/listUserDevices.ts index 65dba7e6..83012fa1 100644 --- a/server/routers/client/listUserDevices.ts +++ b/server/routers/client/listUserDevices.ts @@ -20,10 +20,10 @@ import { asc, desc, eq, - ilike, inArray, isNotNull, isNull, + like, or, sql, type SQL @@ -287,8 +287,14 @@ export async function listUserDevices( if (query) { conditions.push( or( - ilike(clients.name, "%" + query + "%"), - ilike(users.email, "%" + query + "%") + like( + sql`LOWER(${clients.name})`, + "%" + query.toLowerCase() + "%" + ), + like( + sql`LOWER(${users.email})`, + "%" + query.toLowerCase() + "%" + ) ) ); } diff --git a/server/routers/resource/listResources.ts b/server/routers/resource/listResources.ts index 090ea971..66778723 100644 --- a/server/routers/resource/listResources.ts +++ b/server/routers/resource/listResources.ts @@ -1,39 +1,37 @@ -import { Request, Response, NextFunction } from "express"; -import { z } from "zod"; import { db, resourceHeaderAuth, - resourceHeaderAuthExtendedCompatibility -} from "@server/db"; -import { - resources, - userResources, - roleResources, + resourceHeaderAuthExtendedCompatibility, resourcePassword, resourcePincode, + resources, + roleResources, + targetHealthCheck, targets, - targetHealthCheck + userResources } from "@server/db"; import response from "@server/lib/response"; +import logger from "@server/logger"; +import { OpenAPITags, registry } from "@server/openApi"; import HttpCode from "@server/types/HttpCode"; -import createHttpError from "http-errors"; +import type { PaginatedResponse } from "@server/types/Pagination"; import { - sql, - eq, - or, - inArray, and, - count, - ilike, asc, - not, + count, + eq, + inArray, isNull, + like, + not, + or, + sql, type SQL } from "drizzle-orm"; -import logger from "@server/logger"; +import { NextFunction, Request, Response } from "express"; +import createHttpError from "http-errors"; +import { z } from "zod"; import { fromZodError } from "zod-validation-error"; -import { OpenAPITags, registry } from "@server/openApi"; -import type { PaginatedResponse } from "@server/types/Pagination"; const listResourcesParamsSchema = z.strictObject({ orgId: z.string() @@ -278,8 +276,14 @@ export async function listResources( if (query) { conditions.push( or( - ilike(resources.name, "%" + query + "%"), - ilike(resources.fullDomain, "%" + query + "%") + like( + sql`LOWER(${resources.name})`, + "%" + query.toLowerCase() + "%" + ), + like( + sql`LOWER(${resources.fullDomain})`, + "%" + query.toLowerCase() + "%" + ) ) ); } diff --git a/server/routers/site/listSites.ts b/server/routers/site/listSites.ts index cc292499..0bd96cac 100644 --- a/server/routers/site/listSites.ts +++ b/server/routers/site/listSites.ts @@ -1,28 +1,25 @@ -import { db, exitNodes, newts } from "@server/db"; -import { orgs, roleSites, sites, userSites } from "@server/db"; -import { remoteExitNodes } from "@server/db"; -import logger from "@server/logger"; -import HttpCode from "@server/types/HttpCode"; -import response from "@server/lib/response"; import { - and, - asc, - count, - desc, - eq, - ilike, - inArray, - or, - sql -} from "drizzle-orm"; + db, + exitNodes, + newts, + orgs, + remoteExitNodes, + roleSites, + sites, + userSites +} from "@server/db"; +import cache from "@server/lib/cache"; +import response from "@server/lib/response"; +import logger from "@server/logger"; +import { OpenAPITags, registry } from "@server/openApi"; +import HttpCode from "@server/types/HttpCode"; +import type { PaginatedResponse } from "@server/types/Pagination"; +import { and, asc, desc, eq, inArray, like, or, sql } from "drizzle-orm"; import { NextFunction, Request, Response } from "express"; import createHttpError from "http-errors"; +import semver from "semver"; import { z } from "zod"; import { fromError } from "zod-validation-error"; -import { OpenAPITags, registry } from "@server/openApi"; -import semver from "semver"; -import cache from "@server/lib/cache"; -import type { PaginatedResponse } from "@server/types/Pagination"; async function getLatestNewtVersion(): Promise { try { @@ -233,8 +230,14 @@ export async function listSites( if (query) { conditions.push( or( - ilike(sites.name, "%" + query + "%"), - ilike(sites.niceId, "%" + query + "%") + like( + sql`LOWER(${sites.name})`, + "%" + query.toLowerCase() + "%" + ), + like( + sql`LOWER(${sites.niceId})`, + "%" + query.toLowerCase() + "%" + ) ) ); } diff --git a/server/routers/siteResource/listAllSiteResourcesByOrg.ts b/server/routers/siteResource/listAllSiteResourcesByOrg.ts index f15d2ecc..1b376741 100644 --- a/server/routers/siteResource/listAllSiteResourcesByOrg.ts +++ b/server/routers/siteResource/listAllSiteResourcesByOrg.ts @@ -1,15 +1,14 @@ -import { Request, Response, NextFunction } from "express"; -import { z } from "zod"; -import { db, resources } from "@server/db"; -import { siteResources, sites, SiteResource } from "@server/db"; +import { db, SiteResource, siteResources, sites } from "@server/db"; import response from "@server/lib/response"; -import HttpCode from "@server/types/HttpCode"; -import createHttpError from "http-errors"; -import { eq, and, asc, ilike, or } from "drizzle-orm"; -import { fromError } from "zod-validation-error"; import logger from "@server/logger"; import { OpenAPITags, registry } from "@server/openApi"; +import HttpCode from "@server/types/HttpCode"; import type { PaginatedResponse } from "@server/types/Pagination"; +import { and, asc, eq, like, or, sql } from "drizzle-orm"; +import { NextFunction, Request, Response } from "express"; +import createHttpError from "http-errors"; +import { z } from "zod"; +import { fromError } from "zod-validation-error"; const listAllSiteResourcesByOrgParamsSchema = z.strictObject({ orgId: z.string() @@ -118,11 +117,26 @@ export async function listAllSiteResourcesByOrg( if (query) { conditions.push( or( - ilike(siteResources.name, "%" + query + "%"), - ilike(siteResources.destination, "%" + query + "%"), - ilike(siteResources.alias, "%" + query + "%"), - ilike(siteResources.aliasAddress, "%" + query + "%"), - ilike(sites.name, "%" + query + "%") + like( + sql`LOWER(${siteResources.name})`, + "%" + query.toLowerCase() + "%" + ), + like( + sql`LOWER(${siteResources.destination})`, + "%" + query.toLowerCase() + "%" + ), + like( + sql`LOWER(${siteResources.alias})`, + "%" + query.toLowerCase() + "%" + ), + like( + sql`LOWER(${siteResources.aliasAddress})`, + "%" + query.toLowerCase() + "%" + ), + like( + sql`LOWER(${sites.name})`, + "%" + query.toLowerCase() + "%" + ) ) ); } From ddfe55e3aead0203a05c84a57c85aa2aa26403b7 Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Sat, 14 Feb 2026 04:19:30 +0100 Subject: [PATCH 56/81] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20add=20`niceId`=20to?= =?UTF-8?q?=20query=20filtering=20on=20most=20tables?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/routers/client/listClients.ts | 4 ++++ server/routers/client/listUserDevices.ts | 4 ++++ server/routers/resource/listResources.ts | 4 ++++ server/routers/siteResource/listAllSiteResourcesByOrg.ts | 4 ++++ 4 files changed, 16 insertions(+) diff --git a/server/routers/client/listClients.ts b/server/routers/client/listClients.ts index b7eec8e4..c22d31b4 100644 --- a/server/routers/client/listClients.ts +++ b/server/routers/client/listClients.ts @@ -310,6 +310,10 @@ export async function listClients( like( sql`LOWER(${clients.name})`, "%" + query.toLowerCase() + "%" + ), + like( + sql`LOWER(${clients.niceId})`, + "%" + query.toLowerCase() + "%" ) ) ); diff --git a/server/routers/client/listUserDevices.ts b/server/routers/client/listUserDevices.ts index 83012fa1..9db676d4 100644 --- a/server/routers/client/listUserDevices.ts +++ b/server/routers/client/listUserDevices.ts @@ -291,6 +291,10 @@ export async function listUserDevices( sql`LOWER(${clients.name})`, "%" + query.toLowerCase() + "%" ), + like( + sql`LOWER(${clients.niceId})`, + "%" + query.toLowerCase() + "%" + ), like( sql`LOWER(${users.email})`, "%" + query.toLowerCase() + "%" diff --git a/server/routers/resource/listResources.ts b/server/routers/resource/listResources.ts index 66778723..1fa8b316 100644 --- a/server/routers/resource/listResources.ts +++ b/server/routers/resource/listResources.ts @@ -280,6 +280,10 @@ export async function listResources( sql`LOWER(${resources.name})`, "%" + query.toLowerCase() + "%" ), + like( + sql`LOWER(${resources.niceId})`, + "%" + query.toLowerCase() + "%" + ), like( sql`LOWER(${resources.fullDomain})`, "%" + query.toLowerCase() + "%" diff --git a/server/routers/siteResource/listAllSiteResourcesByOrg.ts b/server/routers/siteResource/listAllSiteResourcesByOrg.ts index 1b376741..944955a5 100644 --- a/server/routers/siteResource/listAllSiteResourcesByOrg.ts +++ b/server/routers/siteResource/listAllSiteResourcesByOrg.ts @@ -121,6 +121,10 @@ export async function listAllSiteResourcesByOrg( sql`LOWER(${siteResources.name})`, "%" + query.toLowerCase() + "%" ), + like( + sql`LOWER(${siteResources.niceId})`, + "%" + query.toLowerCase() + "%" + ), like( sql`LOWER(${siteResources.destination})`, "%" + query.toLowerCase() + "%" From d4668fae99ff2626da14f90b6fa128beb75f3b56 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Sat, 14 Feb 2026 11:25:00 -0800 Subject: [PATCH 57/81] add openapi types --- server/routers/client/listClients.ts | 49 ++++++++++++-- server/routers/client/listUserDevices.ts | 66 +++++++++++++++++-- server/routers/resource/listResources.ts | 34 ++++++++-- server/routers/site/listSites.ts | 37 +++++++++-- .../siteResource/listAllSiteResourcesByOrg.ts | 24 ++++++- 5 files changed, 188 insertions(+), 22 deletions(-) diff --git a/server/routers/client/listClients.ts b/server/routers/client/listClients.ts index c22d31b4..53a66150 100644 --- a/server/routers/client/listClients.ts +++ b/server/routers/client/listClients.ts @@ -99,25 +99,54 @@ const listClientsSchema = z.object({ .positive() .optional() .catch(20) - .default(20), + .default(20) + .openapi({ + type: "integer", + default: 20, + description: "Number of items per page" + }), page: z.coerce .number() // for prettier formatting .int() .min(0) .optional() .catch(1) - .default(1), + .default(1) + .openapi({ + type: "integer", + default: 1, + description: "Page number to retrieve" + }), query: z.string().optional(), sort_by: z .enum(["megabytesIn", "megabytesOut"]) .optional() - .catch(undefined), - order: z.enum(["asc", "desc"]).optional().default("asc").catch("asc"), + .catch(undefined) + .openapi({ + type: "string", + enum: ["megabytesIn", "megabytesOut"], + description: "Field to sort by" + }), + order: z + .enum(["asc", "desc"]) + .optional() + .default("asc") + .catch("asc") + .openapi({ + type: "string", + enum: ["asc", "desc"], + default: "asc", + description: "Sort order" + }), online: z .enum(["true", "false"]) .transform((v) => v === "true") .optional() - .catch(undefined), + .catch(undefined) + .openapi({ + type: "boolean", + description: "Filter by online status" + }), status: z.preprocess( (val: string | undefined) => { if (val) { @@ -130,6 +159,16 @@ const listClientsSchema = z.object({ .optional() .default(["active"]) .catch(["active"]) + .openapi({ + type: "array", + items: { + type: "string", + enum: ["active", "blocked", "archived"] + }, + default: ["active"], + description: + "Filter by client status. Can be a comma-separated list of values. Defaults to 'active'." + }) ) }); diff --git a/server/routers/client/listUserDevices.ts b/server/routers/client/listUserDevices.ts index 9db676d4..54fffe43 100644 --- a/server/routers/client/listUserDevices.ts +++ b/server/routers/client/listUserDevices.ts @@ -100,25 +100,54 @@ const listUserDevicesSchema = z.object({ .positive() .optional() .catch(20) - .default(20), + .default(20) + .openapi({ + type: "integer", + default: 20, + description: "Number of items per page" + }), page: z.coerce .number() // for prettier formatting .int() .min(0) .optional() .catch(1) - .default(1), + .default(1) + .openapi({ + type: "integer", + default: 1, + description: "Page number to retrieve" + }), query: z.string().optional(), sort_by: z .enum(["megabytesIn", "megabytesOut"]) .optional() - .catch(undefined), - order: z.enum(["asc", "desc"]).optional().default("asc").catch("asc"), + .catch(undefined) + .openapi({ + type: "string", + enum: ["megabytesIn", "megabytesOut"], + description: "Field to sort by" + }), + order: z + .enum(["asc", "desc"]) + .optional() + .default("asc") + .catch("asc") + .openapi({ + type: "string", + enum: ["asc", "desc"], + default: "asc", + description: "Sort order" + }), online: z .enum(["true", "false"]) .transform((v) => v === "true") .optional() - .catch(undefined), + .catch(undefined) + .openapi({ + type: "boolean", + description: "Filter by online status" + }), agent: z .enum([ "windows", @@ -131,7 +160,22 @@ const listUserDevicesSchema = z.object({ "unknown" ]) .optional() - .catch(undefined), + .catch(undefined) + .openapi({ + type: "string", + enum: [ + "windows", + "android", + "cli", + "olm", + "macos", + "ios", + "ipados", + "unknown" + ], + description: + "Filter by agent type. Use 'unknown' to filter clients with no agent detected." + }), status: z.preprocess( (val: string | undefined) => { if (val) { @@ -146,6 +190,16 @@ const listUserDevicesSchema = z.object({ .optional() .default(["active", "pending"]) .catch(["active", "pending"]) + .openapi({ + type: "array", + items: { + type: "string", + enum: ["active", "pending", "denied", "blocked", "archived"] + }, + default: ["active", "pending"], + description: + "Filter by device status. Can include multiple values separated by commas. 'active' means not archived, not blocked, and if approval is enabled, approved. 'pending' and 'denied' are only applicable if approval is enabled." + }) ) }); diff --git a/server/routers/resource/listResources.ts b/server/routers/resource/listResources.ts index 1fa8b316..a26a5df5 100644 --- a/server/routers/resource/listResources.ts +++ b/server/routers/resource/listResources.ts @@ -44,28 +44,54 @@ const listResourcesSchema = z.object({ .positive() .optional() .catch(20) - .default(20), + .default(20) + .openapi({ + type: "integer", + default: 20, + description: "Number of items per page" + }), page: z.coerce .number() // for prettier formatting .int() .min(0) .optional() .catch(1) - .default(1), + .default(1) + .openapi({ + type: "integer", + default: 1, + description: "Page number to retrieve" + }), query: z.string().optional(), enabled: z .enum(["true", "false"]) .transform((v) => v === "true") .optional() - .catch(undefined), + .catch(undefined) + .openapi({ + type: "boolean", + description: "Filter resources based on enabled status" + }), authState: z .enum(["protected", "not_protected", "none"]) .optional() - .catch(undefined), + .catch(undefined) + .openapi({ + type: "string", + enum: ["protected", "not_protected", "none"], + description: + "Filter resources based on authentication state. `protected` means the resource has at least one auth mechanism (password, pincode, header auth, SSO, or email whitelist). `not_protected` means the resource has no auth mechanisms. `none` means the resource is not protected by HTTP (i.e. it has no auth mechanisms and http is false)." + }), healthStatus: z .enum(["no_targets", "healthy", "degraded", "offline", "unknown"]) .optional() .catch(undefined) + .openapi({ + type: "string", + enum: ["no_targets", "healthy", "degraded", "offline", "unknown"], + description: + "Filter resources based on health status of their targets. `healthy` means all targets are healthy. `degraded` means at least one target is unhealthy, but not all are unhealthy. `offline` means all targets are unhealthy. `unknown` means all targets have unknown health status. `no_targets` means the resource has no targets." + }) }); // grouped by resource with targets[]) diff --git a/server/routers/site/listSites.ts b/server/routers/site/listSites.ts index 0bd96cac..e4881b1a 100644 --- a/server/routers/site/listSites.ts +++ b/server/routers/site/listSites.ts @@ -88,25 +88,54 @@ const listSitesSchema = z.object({ .positive() .optional() .catch(20) - .default(20), + .default(20) + .openapi({ + type: "integer", + default: 20, + description: "Number of items per page" + }), page: z.coerce .number() // for prettier formatting .int() .min(0) .optional() .catch(1) - .default(1), + .default(1) + .openapi({ + type: "integer", + default: 1, + description: "Page number to retrieve" + }), query: z.string().optional(), sort_by: z .enum(["megabytesIn", "megabytesOut"]) .optional() - .catch(undefined), - order: z.enum(["asc", "desc"]).optional().default("asc").catch("asc"), + .catch(undefined) + .openapi({ + type: "string", + enum: ["megabytesIn", "megabytesOut"], + description: "Field to sort by" + }), + order: z + .enum(["asc", "desc"]) + .optional() + .default("asc") + .catch("asc") + .openapi({ + type: "string", + enum: ["asc", "desc"], + default: "asc", + description: "Sort order" + }), online: z .enum(["true", "false"]) .transform((v) => v === "true") .optional() .catch(undefined) + .openapi({ + type: "boolean", + description: "Filter by online status" + }) }); function querySitesBase() { diff --git a/server/routers/siteResource/listAllSiteResourcesByOrg.ts b/server/routers/siteResource/listAllSiteResourcesByOrg.ts index 944955a5..ead1fc8a 100644 --- a/server/routers/siteResource/listAllSiteResourcesByOrg.ts +++ b/server/routers/siteResource/listAllSiteResourcesByOrg.ts @@ -21,16 +21,34 @@ const listAllSiteResourcesByOrgQuerySchema = z.object({ .positive() .optional() .catch(20) - .default(20), + .default(20) + .openapi({ + type: "integer", + default: 20, + description: "Number of items per page" + }), page: z.coerce .number() // for prettier formatting .int() .min(0) .optional() .catch(1) - .default(1), + .default(1) + .openapi({ + type: "integer", + default: 1, + description: "Page number to retrieve" + }), query: z.string().optional(), - mode: z.enum(["host", "cidr"]).optional().catch(undefined) + mode: z + .enum(["host", "cidr"]) + .optional() + .catch(undefined) + .openapi({ + type: "string", + enum: ["host", "cidr"], + description: "Filter site resources by mode" + }) }); export type ListAllSiteResourcesByOrgResponse = PaginatedResponse<{ From 4c8edb80b3e5db16d230626463dc81b49a32f327 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Sat, 14 Feb 2026 11:40:59 -0800 Subject: [PATCH 58/81] dont show table footer in client-side data-table --- src/components/ui/controlled-data-table.tsx | 5 ----- src/components/ui/data-table.tsx | 19 ++++++++----------- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/src/components/ui/controlled-data-table.tsx b/src/components/ui/controlled-data-table.tsx index 4b87a520..a0231bb8 100644 --- a/src/components/ui/controlled-data-table.tsx +++ b/src/components/ui/controlled-data-table.tsx @@ -124,11 +124,6 @@ export function ControlledDataTable({ return initial; }, [filters]); - console.log({ - pagination, - rowCount - }); - const table = useReactTable({ data: rows, columns, diff --git a/src/components/ui/data-table.tsx b/src/components/ui/data-table.tsx index 4d79ba0d..834c56e8 100644 --- a/src/components/ui/data-table.tsx +++ b/src/components/ui/data-table.tsx @@ -320,11 +320,6 @@ export function DataTable({ return result; }, [data, tabs, activeTab, filters, activeFilters]); - console.log({ - pagination, - paginationState - }); - const table = useReactTable({ data: filteredData, columns, @@ -852,12 +847,14 @@ export function DataTable({
    - + {table.getRowModel().rows?.length > 0 && ( + + )}
    From 33f0782f3a3fc24f285f9ad4923c6e48fd2fa496 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Sat, 14 Feb 2026 17:27:51 -0800 Subject: [PATCH 59/81] support delete account --- messages/en-US.json | 12 + server/lib/deleteOrg.ts | 169 +++++++ server/routers/auth/deleteMyAccount.ts | 228 ++++++++++ server/routers/auth/index.ts | 3 +- server/routers/external.ts | 1 + server/routers/org/deleteOrg.ts | 186 +------- .../delete-account/DeleteAccountClient.tsx | 74 ++++ src/app/auth/delete-account/page.tsx | 28 ++ src/components/ApplyInternalRedirect.tsx | 8 +- src/components/DeleteAccountConfirmDialog.tsx | 414 ++++++++++++++++++ src/components/ProfileIcon.tsx | 18 +- src/components/RedirectToOrg.tsx | 3 +- src/lib/internalRedirect.ts | 11 +- 13 files changed, 963 insertions(+), 192 deletions(-) create mode 100644 server/lib/deleteOrg.ts create mode 100644 server/routers/auth/deleteMyAccount.ts create mode 100644 src/app/auth/delete-account/DeleteAccountClient.tsx create mode 100644 src/app/auth/delete-account/page.tsx create mode 100644 src/components/DeleteAccountConfirmDialog.tsx diff --git a/messages/en-US.json b/messages/en-US.json index e68d257c..3e825711 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -201,6 +201,7 @@ "protocolSelect": "Select a protocol", "resourcePortNumber": "Port Number", "resourcePortNumberDescription": "The external port number to proxy requests.", + "back": "Back", "cancel": "Cancel", "resourceConfig": "Configuration Snippets", "resourceConfigDescription": "Copy and paste these configuration snippets to set up the TCP/UDP resource", @@ -246,6 +247,17 @@ "orgErrorDeleteMessage": "An error occurred while deleting the organization.", "orgDeleted": "Organization deleted", "orgDeletedMessage": "The organization and its data has been deleted.", + "deleteAccount": "Delete Account", + "deleteAccountDescription": "Permanently delete your account, all organizations you own, and all data within those organizations. This cannot be undone.", + "deleteAccountButton": "Delete Account", + "deleteAccountConfirmTitle": "Delete Account", + "deleteAccountConfirmMessage": "This will permanently wipe your account, all organizations you own, and all data within those organizations. This cannot be undone.", + "deleteAccountConfirmString": "delete account", + "deleteAccountSuccess": "Account Deleted", + "deleteAccountSuccessMessage": "Your account has been deleted.", + "deleteAccountError": "Failed to delete account", + "deleteAccountPreviewAccount": "Your Account", + "deleteAccountPreviewOrgs": "Organizations you own (and all their data)", "orgMissing": "Organization ID Missing", "orgMissingMessage": "Unable to regenerate invitation without an organization ID.", "accessUsersManage": "Manage Users", diff --git a/server/lib/deleteOrg.ts b/server/lib/deleteOrg.ts new file mode 100644 index 00000000..7295555d --- /dev/null +++ b/server/lib/deleteOrg.ts @@ -0,0 +1,169 @@ +import { + clients, + clientSiteResourcesAssociationsCache, + clientSitesAssociationsCache, + db, + domains, + olms, + orgDomains, + orgs, + resources, + sites +} from "@server/db"; +import { newts, newtSessions } from "@server/db"; +import { eq, and, inArray, sql } from "drizzle-orm"; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import logger from "@server/logger"; +import { sendToClient } from "#dynamic/routers/ws"; +import { deletePeer } from "@server/routers/gerbil/peers"; +import { OlmErrorCodes } from "@server/routers/olm/error"; +import { sendTerminateClient } from "@server/routers/client/terminate"; + +export type DeleteOrgByIdResult = { + deletedNewtIds: string[]; + olmsToTerminate: string[]; +}; + +/** + * Deletes one organization and its related data. Returns ids for termination + * messages; caller should call sendTerminationMessages with the result. + * Throws if org not found. + */ +export async function deleteOrgById( + orgId: string +): Promise { + const [org] = await db + .select() + .from(orgs) + .where(eq(orgs.orgId, orgId)) + .limit(1); + + if (!org) { + throw createHttpError( + HttpCode.NOT_FOUND, + `Organization with ID ${orgId} not found` + ); + } + + const orgSites = await db + .select() + .from(sites) + .where(eq(sites.orgId, orgId)) + .limit(1); + + const orgClients = await db + .select() + .from(clients) + .where(eq(clients.orgId, orgId)); + + const deletedNewtIds: string[] = []; + const olmsToTerminate: string[] = []; + + await db.transaction(async (trx) => { + for (const site of orgSites) { + if (site.pubKey) { + if (site.type == "wireguard") { + await deletePeer(site.exitNodeId!, site.pubKey); + } else if (site.type == "newt") { + const [deletedNewt] = await trx + .delete(newts) + .where(eq(newts.siteId, site.siteId)) + .returning(); + if (deletedNewt) { + deletedNewtIds.push(deletedNewt.newtId); + await trx + .delete(newtSessions) + .where( + eq(newtSessions.newtId, deletedNewt.newtId) + ); + } + } + } + logger.info(`Deleting site ${site.siteId}`); + await trx.delete(sites).where(eq(sites.siteId, site.siteId)); + } + for (const client of orgClients) { + const [olm] = await trx + .select() + .from(olms) + .where(eq(olms.clientId, client.clientId)) + .limit(1); + if (olm) { + olmsToTerminate.push(olm.olmId); + } + logger.info(`Deleting client ${client.clientId}`); + await trx + .delete(clients) + .where(eq(clients.clientId, client.clientId)); + await trx + .delete(clientSiteResourcesAssociationsCache) + .where( + eq( + clientSiteResourcesAssociationsCache.clientId, + client.clientId + ) + ); + await trx + .delete(clientSitesAssociationsCache) + .where( + eq(clientSitesAssociationsCache.clientId, client.clientId) + ); + } + const allOrgDomains = await trx + .select() + .from(orgDomains) + .innerJoin(domains, eq(domains.domainId, orgDomains.domainId)) + .where( + and( + eq(orgDomains.orgId, orgId), + eq(domains.configManaged, false) + ) + ); + const domainIdsToDelete: string[] = []; + for (const orgDomain of allOrgDomains) { + const domainId = orgDomain.domains.domainId; + const orgCount = await trx + .select({ count: sql`count(*)` }) + .from(orgDomains) + .where(eq(orgDomains.domainId, domainId)); + if (orgCount[0].count === 1) { + domainIdsToDelete.push(domainId); + } + } + if (domainIdsToDelete.length > 0) { + await trx + .delete(domains) + .where(inArray(domains.domainId, domainIdsToDelete)); + } + await trx.delete(resources).where(eq(resources.orgId, orgId)); + await trx.delete(orgs).where(eq(orgs.orgId, orgId)); + }); + + return { deletedNewtIds, olmsToTerminate }; +} + +export function sendTerminationMessages(result: DeleteOrgByIdResult): void { + for (const newtId of result.deletedNewtIds) { + sendToClient(newtId, { type: `newt/wg/terminate`, data: {} }).catch( + (error) => { + logger.error( + "Failed to send termination message to newt:", + error + ); + } + ); + } + for (const olmId of result.olmsToTerminate) { + sendTerminateClient( + 0, + OlmErrorCodes.TERMINATED_REKEYED, + olmId + ).catch((error) => { + logger.error( + "Failed to send termination message to olm:", + error + ); + }); + } +} diff --git a/server/routers/auth/deleteMyAccount.ts b/server/routers/auth/deleteMyAccount.ts new file mode 100644 index 00000000..2c37cd09 --- /dev/null +++ b/server/routers/auth/deleteMyAccount.ts @@ -0,0 +1,228 @@ +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import { db, orgs, userOrgs, users } from "@server/db"; +import { eq, and, inArray } from "drizzle-orm"; +import response from "@server/lib/response"; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import logger from "@server/logger"; +import { fromError } from "zod-validation-error"; +import { verifySession } from "@server/auth/sessions/verifySession"; +import { + invalidateSession, + createBlankSessionTokenCookie +} from "@server/auth/sessions/app"; +import { verifyPassword } from "@server/auth/password"; +import { verifyTotpCode } from "@server/auth/totp"; +import { calculateUserClientsForOrgs } from "@server/lib/calculateUserClientsForOrgs"; +import { + deleteOrgById, + sendTerminationMessages +} from "@server/lib/deleteOrg"; +import { UserType } from "@server/types/UserTypes"; + +const deleteMyAccountBody = z.strictObject({ + password: z.string().optional(), + code: z.string().optional() +}); + +export type DeleteMyAccountPreviewResponse = { + preview: true; + orgs: { orgId: string; name: string }[]; + twoFactorEnabled: boolean; +}; + +export type DeleteMyAccountCodeRequestedResponse = { + codeRequested: true; +}; + +export type DeleteMyAccountSuccessResponse = { + success: true; +}; + +/** + * Self-service account deletion (saas only). Returns preview when no password; + * requires password and optional 2FA code to perform deletion. Uses shared + * deleteOrgById for each owned org (delete-my-account may delete multiple orgs). + */ +export async function deleteMyAccount( + req: Request, + res: Response, + next: NextFunction +): Promise { + try { + const { user, session } = await verifySession(req); + if (!user || !session) { + return next( + createHttpError(HttpCode.UNAUTHORIZED, "Not authenticated") + ); + } + + if (user.serverAdmin) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + "Server admins cannot delete their account this way" + ) + ); + } + + if (user.type !== UserType.Internal) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + "Account deletion with password is only supported for internal users" + ) + ); + } + + const parsed = deleteMyAccountBody.safeParse(req.body ?? {}); + if (!parsed.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + fromError(parsed.error).toString() + ) + ); + } + const { password, code } = parsed.data; + + const userId = user.userId; + + const ownedOrgsRows = await db + .select({ + orgId: userOrgs.orgId + }) + .from(userOrgs) + .where( + and( + eq(userOrgs.userId, userId), + eq(userOrgs.isOwner, true) + ) + ); + + const orgIds = ownedOrgsRows.map((r) => r.orgId); + + if (!password) { + const orgsWithNames = + orgIds.length > 0 + ? await db + .select({ + orgId: orgs.orgId, + name: orgs.name + }) + .from(orgs) + .where(inArray(orgs.orgId, orgIds)) + : []; + return response(res, { + data: { + preview: true, + orgs: orgsWithNames.map((o) => ({ + orgId: o.orgId, + name: o.name ?? "" + })), + twoFactorEnabled: user.twoFactorEnabled ?? false + }, + success: true, + error: false, + message: "Preview", + status: HttpCode.OK + }); + } + + const validPassword = await verifyPassword( + password, + user.passwordHash! + ); + if (!validPassword) { + return next( + createHttpError(HttpCode.UNAUTHORIZED, "Invalid password") + ); + } + + if (user.twoFactorEnabled) { + if (!code) { + return response(res, { + data: { codeRequested: true }, + success: true, + error: false, + message: "Two-factor code required", + status: HttpCode.ACCEPTED + }); + } + const validOTP = await verifyTotpCode( + code, + user.twoFactorSecret!, + user.userId + ); + if (!validOTP) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + "The two-factor code you entered is incorrect" + ) + ); + } + } + + const allDeletedNewtIds: string[] = []; + const allOlmsToTerminate: string[] = []; + + for (const row of ownedOrgsRows) { + try { + const result = await deleteOrgById(row.orgId); + allDeletedNewtIds.push(...result.deletedNewtIds); + allOlmsToTerminate.push(...result.olmsToTerminate); + } catch (err) { + logger.error( + `Failed to delete org ${row.orgId} during account deletion`, + err + ); + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "Failed to delete organization" + ) + ); + } + } + + sendTerminationMessages({ + deletedNewtIds: allDeletedNewtIds, + olmsToTerminate: allOlmsToTerminate + }); + + await db.transaction(async (trx) => { + await trx.delete(users).where(eq(users.userId, userId)); + await calculateUserClientsForOrgs(userId, trx); + }); + + try { + await invalidateSession(session.sessionId); + } catch (error) { + logger.error( + "Failed to invalidate session after account deletion", + error + ); + } + + const isSecure = req.protocol === "https"; + res.setHeader("Set-Cookie", createBlankSessionTokenCookie(isSecure)); + + return response(res, { + data: { success: true }, + success: true, + error: false, + message: "Account deleted successfully", + status: HttpCode.OK + }); + } catch (error) { + logger.error(error); + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "An error occurred" + ) + ); + } +} diff --git a/server/routers/auth/index.ts b/server/routers/auth/index.ts index ee08d155..7a469aa1 100644 --- a/server/routers/auth/index.ts +++ b/server/routers/auth/index.ts @@ -17,4 +17,5 @@ export * from "./securityKey"; export * from "./startDeviceWebAuth"; export * from "./verifyDeviceWebAuth"; export * from "./pollDeviceWebAuth"; -export * from "./lookupUser"; \ No newline at end of file +export * from "./lookupUser"; +export * from "./deleteMyAccount"; \ No newline at end of file diff --git a/server/routers/external.ts b/server/routers/external.ts index 52aaa81e..5d25e898 100644 --- a/server/routers/external.ts +++ b/server/routers/external.ts @@ -1171,6 +1171,7 @@ authRouter.post( auth.login ); authRouter.post("/logout", auth.logout); +authRouter.post("/delete-my-account", auth.deleteMyAccount); authRouter.post( "/lookup-user", rateLimit({ diff --git a/server/routers/org/deleteOrg.ts b/server/routers/org/deleteOrg.ts index 48d3102d..0e5b87a2 100644 --- a/server/routers/org/deleteOrg.ts +++ b/server/routers/org/deleteOrg.ts @@ -1,28 +1,12 @@ import { Request, Response, NextFunction } from "express"; import { z } from "zod"; -import { - clients, - clientSiteResourcesAssociationsCache, - clientSitesAssociationsCache, - db, - domains, - olms, - orgDomains, - resources -} from "@server/db"; -import { newts, newtSessions, orgs, sites, userActions } from "@server/db"; -import { eq, and, inArray, sql } from "drizzle-orm"; import response from "@server/lib/response"; import HttpCode from "@server/types/HttpCode"; import createHttpError from "http-errors"; -import { ActionsEnum, checkUserActionPermission } from "@server/auth/actions"; import logger from "@server/logger"; import { fromError } from "zod-validation-error"; -import { sendToClient } from "#dynamic/routers/ws"; -import { deletePeer } from "../gerbil/peers"; import { OpenAPITags, registry } from "@server/openApi"; -import { OlmErrorCodes } from "../olm/error"; -import { sendTerminateClient } from "../client/terminate"; +import { deleteOrgById, sendTerminationMessages } from "@server/lib/deleteOrg"; const deleteOrgSchema = z.strictObject({ orgId: z.string() @@ -56,170 +40,9 @@ export async function deleteOrg( ) ); } - const { orgId } = parsedParams.data; - - const [org] = await db - .select() - .from(orgs) - .where(eq(orgs.orgId, orgId)) - .limit(1); - - if (!org) { - return next( - createHttpError( - HttpCode.NOT_FOUND, - `Organization with ID ${orgId} not found` - ) - ); - } - // we need to handle deleting each site - const orgSites = await db - .select() - .from(sites) - .where(eq(sites.orgId, orgId)) - .limit(1); - - const orgClients = await db - .select() - .from(clients) - .where(eq(clients.orgId, orgId)); - - const deletedNewtIds: string[] = []; - const olmsToTerminate: string[] = []; - - await db.transaction(async (trx) => { - for (const site of orgSites) { - if (site.pubKey) { - if (site.type == "wireguard") { - await deletePeer(site.exitNodeId!, site.pubKey); - } else if (site.type == "newt") { - // get the newt on the site by querying the newt table for siteId - const [deletedNewt] = await trx - .delete(newts) - .where(eq(newts.siteId, site.siteId)) - .returning(); - if (deletedNewt) { - deletedNewtIds.push(deletedNewt.newtId); - - // delete all of the sessions for the newt - await trx - .delete(newtSessions) - .where( - eq(newtSessions.newtId, deletedNewt.newtId) - ); - } - } - } - - logger.info(`Deleting site ${site.siteId}`); - await trx.delete(sites).where(eq(sites.siteId, site.siteId)); - } - for (const client of orgClients) { - const [olm] = await trx - .select() - .from(olms) - .where(eq(olms.clientId, client.clientId)) - .limit(1); - - if (olm) { - olmsToTerminate.push(olm.olmId); - } - - logger.info(`Deleting client ${client.clientId}`); - await trx - .delete(clients) - .where(eq(clients.clientId, client.clientId)); - - // also delete the associations - await trx - .delete(clientSiteResourcesAssociationsCache) - .where( - eq( - clientSiteResourcesAssociationsCache.clientId, - client.clientId - ) - ); - - await trx - .delete(clientSitesAssociationsCache) - .where( - eq( - clientSitesAssociationsCache.clientId, - client.clientId - ) - ); - } - - const allOrgDomains = await trx - .select() - .from(orgDomains) - .innerJoin(domains, eq(domains.domainId, orgDomains.domainId)) - .where( - and( - eq(orgDomains.orgId, orgId), - eq(domains.configManaged, false) - ) - ); - - // For each domain, check if it belongs to multiple organizations - const domainIdsToDelete: string[] = []; - for (const orgDomain of allOrgDomains) { - const domainId = orgDomain.domains.domainId; - - // Count how many organizations this domain belongs to - const orgCount = await trx - .select({ count: sql`count(*)` }) - .from(orgDomains) - .where(eq(orgDomains.domainId, domainId)); - - // Only delete the domain if it belongs to exactly 1 organization (the one being deleted) - if (orgCount[0].count === 1) { - domainIdsToDelete.push(domainId); - } - } - - // Delete domains that belong exclusively to this organization - if (domainIdsToDelete.length > 0) { - await trx - .delete(domains) - .where(inArray(domains.domainId, domainIdsToDelete)); - } - - // Delete resources - await trx.delete(resources).where(eq(resources.orgId, orgId)); - - await trx.delete(orgs).where(eq(orgs.orgId, orgId)); - }); - - // Send termination messages outside of transaction to prevent blocking - for (const newtId of deletedNewtIds) { - const payload = { - type: `newt/wg/terminate`, - data: {} - }; - // Don't await this to prevent blocking the response - sendToClient(newtId, payload).catch((error) => { - logger.error( - "Failed to send termination message to newt:", - error - ); - }); - } - - for (const olmId of olmsToTerminate) { - sendTerminateClient( - 0, // clientId not needed since we're passing olmId - OlmErrorCodes.TERMINATED_REKEYED, - olmId - ).catch((error) => { - logger.error( - "Failed to send termination message to olm:", - error - ); - }); - } - + const result = await deleteOrgById(orgId); + sendTerminationMessages(result); return response(res, { data: null, success: true, @@ -228,6 +51,9 @@ export async function deleteOrg( status: HttpCode.OK }); } catch (error) { + if (createHttpError.isHttpError(error)) { + return next(error); + } logger.error(error); return next( createHttpError( diff --git a/src/app/auth/delete-account/DeleteAccountClient.tsx b/src/app/auth/delete-account/DeleteAccountClient.tsx new file mode 100644 index 00000000..8cd150af --- /dev/null +++ b/src/app/auth/delete-account/DeleteAccountClient.tsx @@ -0,0 +1,74 @@ +"use client"; + +import { useState } from "react"; +import { useRouter } from "next/navigation"; +import { useTranslations } from "next-intl"; +import { Button } from "@app/components/ui/button"; +import DeleteAccountConfirmDialog from "@app/components/DeleteAccountConfirmDialog"; +import UserProfileCard from "@app/components/UserProfileCard"; +import { ArrowLeft } from "lucide-react"; +import { createApiClient } from "@app/lib/api"; +import { useEnvContext } from "@app/hooks/useEnvContext"; +import { toast } from "@app/hooks/useToast"; +import { formatAxiosError } from "@app/lib/api"; + +type DeleteAccountClientProps = { + displayName: string; +}; + +export default function DeleteAccountClient({ + displayName +}: DeleteAccountClientProps) { + const router = useRouter(); + const t = useTranslations(); + const { env } = useEnvContext(); + const api = createApiClient({ env }); + const [isDialogOpen, setIsDialogOpen] = useState(false); + + function handleUseDifferentAccount() { + api.post("/auth/logout") + .catch((e) => { + console.error(t("logoutError"), e); + toast({ + title: t("logoutError"), + description: formatAxiosError(e, t("logoutError")) + }); + }) + .then(() => { + router.push( + "/auth/login?internal_redirect=/auth/delete-account" + ); + router.refresh(); + }); + } + + return ( +
    + +

    + {t("deleteAccountDescription")} +

    +
    + + +
    + +
    + ); +} diff --git a/src/app/auth/delete-account/page.tsx b/src/app/auth/delete-account/page.tsx new file mode 100644 index 00000000..5cbc8d73 --- /dev/null +++ b/src/app/auth/delete-account/page.tsx @@ -0,0 +1,28 @@ +import { verifySession } from "@app/lib/auth/verifySession"; +import { redirect } from "next/navigation"; +import { build } from "@server/build"; +import { cache } from "react"; +import DeleteAccountClient from "./DeleteAccountClient"; +import { getTranslations } from "next-intl/server"; +import { getUserDisplayName } from "@app/lib/getUserDisplayName"; + +export const dynamic = "force-dynamic"; + +export default async function DeleteAccountPage() { + const getUser = cache(verifySession); + const user = await getUser({ skipCheckVerifyEmail: true }); + + if (!user) { + redirect("/auth/login"); + } + + const t = await getTranslations(); + const displayName = getUserDisplayName({ user }); + + return ( +
    +

    {t("deleteAccount")}

    + +
    + ); +} diff --git a/src/components/ApplyInternalRedirect.tsx b/src/components/ApplyInternalRedirect.tsx index f2afc8cb..24e93336 100644 --- a/src/components/ApplyInternalRedirect.tsx +++ b/src/components/ApplyInternalRedirect.tsx @@ -2,7 +2,7 @@ import { useEffect } from "react"; import { useRouter } from "next/navigation"; -import { consumeInternalRedirectPath } from "@app/lib/internalRedirect"; +import { getInternalRedirectTarget } from "@app/lib/internalRedirect"; type ApplyInternalRedirectProps = { orgId: string; @@ -14,9 +14,9 @@ export default function ApplyInternalRedirect({ const router = useRouter(); useEffect(() => { - const path = consumeInternalRedirectPath(); - if (path) { - router.replace(`/${orgId}${path}`); + const target = getInternalRedirectTarget(orgId); + if (target) { + router.replace(target); } }, [orgId, router]); diff --git a/src/components/DeleteAccountConfirmDialog.tsx b/src/components/DeleteAccountConfirmDialog.tsx new file mode 100644 index 00000000..7a54f9a0 --- /dev/null +++ b/src/components/DeleteAccountConfirmDialog.tsx @@ -0,0 +1,414 @@ +"use client"; + +import { useState, useEffect, useMemo } from "react"; +import { useRouter } from "next/navigation"; +import { createApiClient } from "@app/lib/api"; +import { useEnvContext } from "@app/hooks/useEnvContext"; +import { formatAxiosError } from "@app/lib/api"; +import { toast } from "@app/hooks/useToast"; +import { useTranslations } from "next-intl"; +import { Button } from "@app/components/ui/button"; +import { + Credenza, + CredenzaBody, + CredenzaClose, + CredenzaContent, + CredenzaFooter, + CredenzaHeader, + CredenzaTitle +} from "@app/components/Credenza"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage +} from "@app/components/ui/form"; +import { Input } from "@app/components/ui/input"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; +import { + InputOTP, + InputOTPGroup, + InputOTPSlot +} from "@app/components/ui/input-otp"; +import { REGEXP_ONLY_DIGITS_AND_CHARS } from "input-otp"; +import type { + DeleteMyAccountPreviewResponse, + DeleteMyAccountCodeRequestedResponse, + DeleteMyAccountSuccessResponse +} from "@server/routers/auth/deleteMyAccount"; +import { AxiosResponse } from "axios"; + +type DeleteAccountConfirmDialogProps = { + open: boolean; + setOpen: (open: boolean) => void; +}; + +export default function DeleteAccountConfirmDialog({ + open, + setOpen +}: DeleteAccountConfirmDialogProps) { + const { env } = useEnvContext(); + const api = createApiClient({ env }); + const router = useRouter(); + const t = useTranslations(); + + const passwordSchema = useMemo( + () => + z.object({ + password: z.string().min(1, { message: t("passwordRequired") }) + }), + [t] + ); + + const codeSchema = useMemo( + () => + z.object({ + code: z.string().length(6, { message: t("pincodeInvalid") }) + }), + [t] + ); + + const [step, setStep] = useState<0 | 1 | 2>(0); + const [loading, setLoading] = useState(false); + const [loadingPreview, setLoadingPreview] = useState(false); + const [preview, setPreview] = + useState(null); + const [passwordValue, setPasswordValue] = useState(""); + + const passwordForm = useForm>({ + resolver: zodResolver(passwordSchema), + defaultValues: { password: "" } + }); + + const codeForm = useForm>({ + resolver: zodResolver(codeSchema), + defaultValues: { code: "" } + }); + + useEffect(() => { + if (open && step === 0 && !preview) { + setLoadingPreview(true); + api.post>( + "/auth/delete-my-account", + {} + ) + .then((res) => { + if (res.data?.data?.preview) { + setPreview(res.data.data); + } + }) + .catch((err) => { + toast({ + variant: "destructive", + title: t("deleteAccountError"), + description: formatAxiosError( + err, + t("deleteAccountError") + ) + }); + setOpen(false); + }) + .finally(() => setLoadingPreview(false)); + } + }, [open, step, preview, api, setOpen, t]); + + function reset() { + setStep(0); + setPreview(null); + setPasswordValue(""); + passwordForm.reset(); + codeForm.reset(); + } + + async function handleContinueToPassword() { + setStep(1); + } + + async function handlePasswordSubmit( + values: z.infer + ) { + setLoading(true); + setPasswordValue(values.password); + try { + const res = await api.post< + | AxiosResponse + | AxiosResponse + >("/auth/delete-my-account", { password: values.password }); + + const data = res.data?.data; + + if (data && "codeRequested" in data && data.codeRequested) { + setStep(2); + } else if (data && "success" in data && data.success) { + toast({ + title: t("deleteAccountSuccess"), + description: t("deleteAccountSuccessMessage") + }); + setOpen(false); + reset(); + router.push("/auth/login"); + router.refresh(); + } + } catch (err) { + toast({ + variant: "destructive", + title: t("deleteAccountError"), + description: formatAxiosError(err, t("deleteAccountError")) + }); + } finally { + setLoading(false); + } + } + + async function handleCodeSubmit(values: z.infer) { + setLoading(true); + try { + const res = await api.post< + AxiosResponse + >("/auth/delete-my-account", { + password: passwordValue, + code: values.code + }); + + if (res.data?.data?.success) { + toast({ + title: t("deleteAccountSuccess"), + description: t("deleteAccountSuccessMessage") + }); + setOpen(false); + reset(); + router.push("/auth/login"); + router.refresh(); + } + } catch (err) { + toast({ + variant: "destructive", + title: t("deleteAccountError"), + description: formatAxiosError(err, t("deleteAccountError")) + }); + } finally { + setLoading(false); + } + } + + return ( + { + setOpen(val); + if (!val) reset(); + }} + > + + + + {t("deleteAccountConfirmTitle")} + + + +
    + {step === 0 && ( + <> + {loadingPreview ? ( +

    + {t("loading")}... +

    + ) : preview ? ( + <> +

    + {t("deleteAccountConfirmMessage")} +

    +
    +

    + {t( + "deleteAccountPreviewAccount" + )} +

    + {preview.orgs.length > 0 && ( + <> +

    + {t( + "deleteAccountPreviewOrgs" + )} +

    +
      + {preview.orgs.map( + (org) => ( +
    • + {org.name || + org.orgId} +
    • + ) + )} +
    + + )} +
    +

    + {t("cannotbeUndone")} +

    + + ) : null} + + )} + + {step === 1 && ( +
    + + ( + + + {t("password")} + + + + + + + )} + /> + + + )} + + {step === 2 && ( +
    +
    +

    + {t("otpAuthDescription")} +

    +
    +
    + + ( + + +
    + { + field.onChange( + value + ); + }} + > + + + + + + + + + +
    +
    + +
    + )} + /> + + +
    + )} +
    +
    + + + + + {step === 0 && preview && !loadingPreview && ( + + )} + {step === 1 && ( + + )} + {step === 2 && ( + + )} + +
    +
    + ); +} diff --git a/src/components/ProfileIcon.tsx b/src/components/ProfileIcon.tsx index d466b707..4c900c62 100644 --- a/src/components/ProfileIcon.tsx +++ b/src/components/ProfileIcon.tsx @@ -15,9 +15,11 @@ import { useEnvContext } from "@app/hooks/useEnvContext"; import { toast } from "@app/hooks/useToast"; import { formatAxiosError } from "@app/lib/api"; import { getUserDisplayName } from "@app/lib/getUserDisplayName"; -import { Laptop, LogOut, Moon, Sun, Smartphone } from "lucide-react"; +import { Laptop, LogOut, Moon, Sun, Smartphone, Trash2 } from "lucide-react"; import { useTheme } from "next-themes"; import { useRouter } from "next/navigation"; +import Link from "next/link"; +import { build } from "@server/build"; import { useState } from "react"; import { useUserContext } from "@app/hooks/useUserContext"; import Disable2FaForm from "./Disable2FaForm"; @@ -187,6 +189,20 @@ export default function ProfileIcon() { + {user?.type === UserType.Internal && !user?.serverAdmin && ( + <> + + + + {t("deleteAccount")} + + + + + )} logout()}> {/* */} {t("logout")} diff --git a/src/components/RedirectToOrg.tsx b/src/components/RedirectToOrg.tsx index 7ea1ea4b..e647ee7a 100644 --- a/src/components/RedirectToOrg.tsx +++ b/src/components/RedirectToOrg.tsx @@ -13,7 +13,8 @@ export default function RedirectToOrg({ targetOrgId }: RedirectToOrgProps) { useEffect(() => { try { - const target = getInternalRedirectTarget(targetOrgId); + const target = + getInternalRedirectTarget(targetOrgId) ?? `/${targetOrgId}`; router.replace(target); } catch { router.replace(`/${targetOrgId}`); diff --git a/src/lib/internalRedirect.ts b/src/lib/internalRedirect.ts index 115cea5c..6514db66 100644 --- a/src/lib/internalRedirect.ts +++ b/src/lib/internalRedirect.ts @@ -41,11 +41,12 @@ export function consumeInternalRedirectPath(): string | null { } /** - * Returns the full redirect target for an org: either `/${orgId}` or - * `/${orgId}${path}` if a valid internal_redirect was stored. Consumes the - * stored value. + * Returns the full redirect target if a valid internal_redirect was stored + * (consumes the stored value). Returns null if none was stored or expired. + * Paths starting with /auth/ are returned as-is; others are prefixed with orgId. */ -export function getInternalRedirectTarget(orgId: string): string { +export function getInternalRedirectTarget(orgId: string): string | null { const path = consumeInternalRedirectPath(); - return path ? `/${orgId}${path}` : `/${orgId}`; + if (!path) return null; + return path.startsWith("/auth/") ? path : `/${orgId}${path}`; } From 9cf59c409e1c8bab77bb8cbf7283194a8e616877 Mon Sep 17 00:00:00 2001 From: Owen Date: Mon, 16 Feb 2026 15:19:29 -0800 Subject: [PATCH 60/81] Initial sign endpoint working --- package-lock.json | 177 ++++++++- package.json | 2 + server/auth/actions.ts | 3 +- server/auth/canUserAccessSiteResource.ts | 45 +++ server/db/pg/schema/schema.ts | 4 +- server/db/sqlite/schema/schema.ts | 4 +- server/lib/billing/tierMatrix.ts | 6 +- server/lib/createUserAccountOrg.ts | 11 +- server/openApi.ts | 3 +- server/private/lib/sshCA.ts | 442 +++++++++++++++++++++++ server/private/routers/external.ts | 12 + server/private/routers/ssh/index.ts | 14 + server/private/routers/ssh/signSshKey.ts | 265 ++++++++++++++ server/routers/org/createOrg.ts | 11 +- 14 files changed, 985 insertions(+), 14 deletions(-) create mode 100644 server/auth/canUserAccessSiteResource.ts create mode 100644 server/private/lib/sshCA.ts create mode 100644 server/private/routers/ssh/index.ts create mode 100644 server/private/routers/ssh/signSshKey.ts diff --git a/package-lock.json b/package-lock.json index cb1c84b9..dabdcd33 100644 --- a/package-lock.json +++ b/package-lock.json @@ -89,6 +89,7 @@ "reodotdev": "1.0.0", "resend": "6.9.2", "semver": "7.7.4", + "sshpk": "^1.18.0", "stripe": "20.3.1", "swagger-ui-express": "5.0.1", "tailwind-merge": "3.4.0", @@ -129,6 +130,7 @@ "@types/react": "19.2.14", "@types/react-dom": "19.2.3", "@types/semver": "7.7.1", + "@types/sshpk": "^1.17.4", "@types/swagger-ui-express": "4.1.8", "@types/topojson-client": "3.1.5", "@types/ws": "8.18.1", @@ -1084,6 +1086,7 @@ "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.26.2", @@ -2815,6 +2818,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -2837,6 +2841,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -2859,6 +2864,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2875,6 +2881,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2891,6 +2898,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2907,6 +2915,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2923,6 +2932,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2939,6 +2949,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2955,6 +2966,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2971,6 +2983,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2987,6 +3000,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -3003,6 +3017,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -3025,6 +3040,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -3047,6 +3063,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -3069,6 +3086,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -3091,6 +3109,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -3113,6 +3132,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -3135,6 +3155,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -3157,6 +3178,7 @@ "cpu": [ "wasm32" ], + "dev": true, "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", "optional": true, "dependencies": { @@ -3176,6 +3198,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ @@ -3195,6 +3218,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ @@ -3214,6 +3238,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ @@ -3495,6 +3520,7 @@ "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": "^14.21.3 || >=16" }, @@ -7924,6 +7950,7 @@ "resolved": "https://registry.npmjs.org/@react-email/text/-/text-0.1.6.tgz", "integrity": "sha512-TYqkioRS45wTR5il3dYk/SbUjjEdhSwh9BtRNB99qNH1pXAwA45H7rAuxehiu8iJQJH0IyIr+6n62gBz9ezmsw==", "license": "MIT", + "peer": true, "engines": { "node": ">=20.0.0" }, @@ -9342,6 +9369,7 @@ "version": "5.90.21", "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.21.tgz", "integrity": "sha512-0Lu6y5t+tvlTJMTO7oh5NSpJfpg/5D41LlThfepTixPYkJ0sE2Jj0m0f6yYqujBwIXlId87e234+MxG3D3g7kg==", + "peer": true, "dependencies": { "@tanstack/query-core": "5.90.20" }, @@ -9441,12 +9469,23 @@ "tslib": "^2.4.0" } }, + "node_modules/@types/asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@types/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-V91DSJ2l0h0gRhVP4oBfBzRBN9lAbPUkGDMCnwedqPKX2d84aAMc9CulOvxdw1f7DfEYx99afab+Rsm3e52jhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/better-sqlite3": { "version": "7.6.13", "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz", "integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "@types/node": "*" } @@ -9787,6 +9826,7 @@ "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^5.0.0", @@ -9881,6 +9921,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.3.tgz", "integrity": "sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==", "devOptional": true, + "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -9908,6 +9949,7 @@ "integrity": "sha512-RmhMd/wD+CF8Dfo+cVIy3RR5cl8CyfXQ0tGgW6XBL8L4LM/UTEbNXYRbLwU6w+CgrKBNbrQWt4FUtTfaU5jSYQ==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "@types/node": "*", "pg-protocol": "*", @@ -9933,6 +9975,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "devOptional": true, + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -9943,6 +9986,7 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "devOptional": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -9975,6 +10019,17 @@ "@types/node": "*" } }, + "node_modules/@types/sshpk": { + "version": "1.17.4", + "resolved": "https://registry.npmjs.org/@types/sshpk/-/sshpk-1.17.4.tgz", + "integrity": "sha512-5gI/7eJn6wmkuIuFY8JZJ1g5b30H9K5U5vKrvOuYu+hoZLb2xcVEgxhYZ2Vhbs0w/ACyzyfkJq0hQtBfSCugjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/asn1": "*", + "@types/node": "*" + } + }, "node_modules/@types/swagger-ui-express": { "version": "4.1.8", "resolved": "https://registry.npmjs.org/@types/swagger-ui-express/-/swagger-ui-express-4.1.8.tgz", @@ -10018,8 +10073,7 @@ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", "license": "MIT", - "optional": true, - "peer": true + "optional": true }, "node_modules/@types/ws": { "version": "8.18.1", @@ -10090,6 +10144,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.55.0.tgz", "integrity": "sha512-4z2nCSBfVIMnbuu8uinj+f0o4qOeggYJLbjpPHka3KH1om7e+H9yLKTYgksTaHcGco+NClhhY2vyO3HsMH1RGw==", "dev": true, + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.55.0", "@typescript-eslint/types": "8.55.0", @@ -10579,6 +10634,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -10919,6 +10975,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, "node_modules/asn1js": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.6.tgz", @@ -10933,6 +10998,15 @@ "node": ">=12.0.0" } }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, "node_modules/ast-types-flow": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", @@ -11025,6 +11099,7 @@ "integrity": "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/types": "^7.26.0" } @@ -11076,12 +11151,22 @@ "baseline-browser-mapping": "dist/cli.js" } }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "license": "BSD-3-Clause", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, "node_modules/better-sqlite3": { "version": "11.9.1", "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-11.9.1.tgz", "integrity": "sha512-Ba0KR+Fzxh2jDRhdg6TSH0SJGzb8C0aBY4hR8w8madIdIzzC6Y1+kx5qR6eS1Z+Gy20h6ZU28aeyg0z1VIrShQ==", "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "bindings": "^1.5.0", "prebuild-install": "^7.1.1" @@ -11208,6 +11293,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -12161,6 +12247,7 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", + "peer": true, "engines": { "node": ">=12" } @@ -12252,6 +12339,18 @@ "dev": true, "license": "BSD-2-Clause" }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/data-view-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", @@ -12589,7 +12688,6 @@ "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz", "integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==", "license": "(MPL-2.0 OR Apache-2.0)", - "peer": true, "optionalDependencies": { "@types/trusted-types": "^2.0.7" } @@ -13275,6 +13373,16 @@ "node": ">= 0.4" } }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "license": "MIT", + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", @@ -13693,6 +13801,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -13791,6 +13900,7 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -13976,6 +14086,7 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -14295,6 +14406,7 @@ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", + "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", @@ -14935,6 +15047,15 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0" + } + }, "node_modules/github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", @@ -15954,6 +16075,12 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", + "license": "MIT" + }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -16789,7 +16916,6 @@ "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz", "integrity": "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==", "license": "MIT", - "peer": true, "dependencies": { "dompurify": "3.2.7", "marked": "14.0.0" @@ -16800,7 +16926,6 @@ "resolved": "https://registry.npmjs.org/marked/-/marked-14.0.0.tgz", "integrity": "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==", "license": "MIT", - "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -16888,6 +17013,7 @@ "version": "15.5.12", "resolved": "https://registry.npmjs.org/next/-/next-15.5.12.tgz", "integrity": "sha512-Fi/wQ4Etlrn60rz78bebG1i1SR20QxvV8tVp6iJspjLUSHcZoeUXCt+vmWoEcza85ElZzExK/jJ/F6SvtGktjA==", + "peer": true, "dependencies": { "@next/env": "15.5.12", "@swc/helpers": "0.5.15", @@ -17822,6 +17948,7 @@ "resolved": "https://registry.npmjs.org/pg/-/pg-8.18.0.tgz", "integrity": "sha512-xqrUDL1b9MbkydY/s+VZ6v+xiMUmOUk7SS9d/1kpyQxoJ6U9AO1oIJyUWVZojbfe5Cc/oluutcgFG4L9RDP1iQ==", "license": "MIT", + "peer": true, "dependencies": { "pg-connection-string": "^2.11.0", "pg-pool": "^3.11.0", @@ -18316,6 +18443,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -18345,6 +18473,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -19161,6 +19290,7 @@ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.71.1.tgz", "integrity": "sha512-9SUJKCGKo8HUSsCO+y0CtqkqI5nNuaDqTxyqPsZPqIwudpj4rCrAz/jZV+jn57bx5gtZKOh3neQu94DXMc+w5w==", "license": "MIT", + "peer": true, "engines": { "node": ">=18.0.0" }, @@ -20187,6 +20317,31 @@ "node": ">= 10.x" } }, + "node_modules/sshpk": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", + "license": "MIT", + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/stable-hash": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", @@ -20605,7 +20760,8 @@ "version": "4.1.18", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/tapable": { "version": "2.3.0", @@ -20946,6 +21102,12 @@ "url": "https://github.com/sponsors/Wombosvideo" } }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "license": "Unlicense" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -21073,6 +21235,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -21499,6 +21662,7 @@ "resolved": "https://registry.npmjs.org/winston/-/winston-3.19.0.tgz", "integrity": "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==", "license": "MIT", + "peer": true, "dependencies": { "@colors/colors": "^1.6.0", "@dabh/diagnostics": "^2.0.8", @@ -21705,6 +21869,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/package.json b/package.json index c8af24ba..f7ac6fbc 100644 --- a/package.json +++ b/package.json @@ -112,6 +112,7 @@ "reodotdev": "1.0.0", "resend": "6.9.2", "semver": "7.7.4", + "sshpk": "^1.18.0", "stripe": "20.3.1", "swagger-ui-express": "5.0.1", "tailwind-merge": "3.4.0", @@ -152,6 +153,7 @@ "@types/react": "19.2.14", "@types/react-dom": "19.2.3", "@types/semver": "7.7.1", + "@types/sshpk": "^1.17.4", "@types/swagger-ui-express": "4.1.8", "@types/topojson-client": "3.1.5", "@types/ws": "8.18.1", diff --git a/server/auth/actions.ts b/server/auth/actions.ts index 094437f4..3f5a145b 100644 --- a/server/auth/actions.ts +++ b/server/auth/actions.ts @@ -131,7 +131,8 @@ export enum ActionsEnum { viewLogs = "viewLogs", exportLogs = "exportLogs", listApprovals = "listApprovals", - updateApprovals = "updateApprovals" + updateApprovals = "updateApprovals", + signSshKey = "signSshKey" } export async function checkUserActionPermission( diff --git a/server/auth/canUserAccessSiteResource.ts b/server/auth/canUserAccessSiteResource.ts new file mode 100644 index 00000000..959b0eff --- /dev/null +++ b/server/auth/canUserAccessSiteResource.ts @@ -0,0 +1,45 @@ +import { db } from "@server/db"; +import { and, eq } from "drizzle-orm"; +import { roleSiteResources, userSiteResources } from "@server/db"; + +export async function canUserAccessSiteResource({ + userId, + resourceId, + roleId +}: { + userId: string; + resourceId: number; + roleId: number; +}): Promise { + const roleResourceAccess = await db + .select() + .from(roleSiteResources) + .where( + and( + eq(roleSiteResources.siteResourceId, resourceId), + eq(roleSiteResources.roleId, roleId) + ) + ) + .limit(1); + + if (roleResourceAccess.length > 0) { + return true; + } + + const userResourceAccess = await db + .select() + .from(userSiteResources) + .where( + and( + eq(userSiteResources.userId, userId), + eq(userSiteResources.siteResourceId, resourceId) + ) + ) + .limit(1); + + if (userResourceAccess.length > 0) { + return true; + } + + return false; +} diff --git a/server/db/pg/schema/schema.ts b/server/db/pg/schema/schema.ts index 6afd463e..4188d894 100644 --- a/server/db/pg/schema/schema.ts +++ b/server/db/pg/schema/schema.ts @@ -53,7 +53,9 @@ export const orgs = pgTable("orgs", { .default(0), settingsLogRetentionDaysAction: integer("settingsLogRetentionDaysAction") // where 0 = dont keep logs and -1 = keep forever and 9001 = end of the following year .notNull() - .default(0) + .default(0), + sshCaPrivateKey: text("sshCaPrivateKey"), // Encrypted SSH CA private key (PEM format) + sshCaPublicKey: text("sshCaPublicKey") // SSH CA public key (OpenSSH format) }); export const orgDomains = pgTable("orgDomains", { diff --git a/server/db/sqlite/schema/schema.ts b/server/db/sqlite/schema/schema.ts index 7335f666..6d60ec68 100644 --- a/server/db/sqlite/schema/schema.ts +++ b/server/db/sqlite/schema/schema.ts @@ -45,7 +45,9 @@ export const orgs = sqliteTable("orgs", { .default(0), settingsLogRetentionDaysAction: integer("settingsLogRetentionDaysAction") // where 0 = dont keep logs and -1 = keep forever and 9001 = end of the following year .notNull() - .default(0) + .default(0), + sshCaPrivateKey: text("sshCaPrivateKey"), // Encrypted SSH CA private key (PEM format) + sshCaPublicKey: text("sshCaPublicKey") // SSH CA public key (OpenSSH format) }); export const userDomains = sqliteTable("userDomains", { diff --git a/server/lib/billing/tierMatrix.ts b/server/lib/billing/tierMatrix.ts index d1fe362a..20f8001d 100644 --- a/server/lib/billing/tierMatrix.ts +++ b/server/lib/billing/tierMatrix.ts @@ -14,7 +14,8 @@ export enum TierFeature { TwoFactorEnforcement = "twoFactorEnforcement", // handle downgrade by setting to optional SessionDurationPolicies = "sessionDurationPolicies", // handle downgrade by setting to default duration PasswordExpirationPolicies = "passwordExpirationPolicies", // handle downgrade by setting to default duration - AutoProvisioning = "autoProvisioning" // handle downgrade by disabling auto provisioning + AutoProvisioning = "autoProvisioning", // handle downgrade by disabling auto provisioning + SshPam = "sshPam" } export const tierMatrix: Record = { @@ -46,5 +47,6 @@ export const tierMatrix: Record = { "tier3", "enterprise" ], - [TierFeature.AutoProvisioning]: ["tier1", "tier3", "enterprise"] + [TierFeature.AutoProvisioning]: ["tier1", "tier3", "enterprise"], + [TierFeature.SshPam]: ["enterprise"] }; diff --git a/server/lib/createUserAccountOrg.ts b/server/lib/createUserAccountOrg.ts index 53f2ea3d..a40407d1 100644 --- a/server/lib/createUserAccountOrg.ts +++ b/server/lib/createUserAccountOrg.ts @@ -19,6 +19,8 @@ import { FeatureId, limitsService, sandboxLimitSet } from "@server/lib/billing"; import { createCustomer } from "#dynamic/lib/billing"; import { usageService } from "@server/lib/billing/usageService"; import config from "@server/lib/config"; +import { generateCA } from "@server/private/lib/sshCA"; +import { encrypt } from "@server/lib/crypto"; export async function createUserAccountOrg( userId: string, @@ -79,6 +81,11 @@ export async function createUserAccountOrg( const utilitySubnet = config.getRawConfig().orgs.utility_subnet_group; + // Generate SSH CA keys for the org + const ca = generateCA(`${orgId}-ca`); + const encryptionKey = config.getRawConfig().server.secret!; + const encryptedCaPrivateKey = encrypt(ca.privateKeyPem, encryptionKey); + const newOrg = await trx .insert(orgs) .values({ @@ -87,7 +94,9 @@ export async function createUserAccountOrg( // subnet subnet: "100.90.128.0/24", // TODO: this should not be hardcoded - or can it be the same in all orgs? utilitySubnet: utilitySubnet, - createdAt: new Date().toISOString() + createdAt: new Date().toISOString(), + sshCaPrivateKey: encryptedCaPrivateKey, + sshCaPublicKey: ca.publicKeyOpenSSH }) .returning(); diff --git a/server/openApi.ts b/server/openApi.ts index 68b05a30..88626568 100644 --- a/server/openApi.ts +++ b/server/openApi.ts @@ -16,5 +16,6 @@ export enum OpenAPITags { Client = "Client", ApiKey = "API Key", Domain = "Domain", - Blueprint = "Blueprint" + Blueprint = "Blueprint", + Ssh = "SSH" } diff --git a/server/private/lib/sshCA.ts b/server/private/lib/sshCA.ts new file mode 100644 index 00000000..145dac61 --- /dev/null +++ b/server/private/lib/sshCA.ts @@ -0,0 +1,442 @@ +/* + * This file is part of a proprietary work. + * + * Copyright (c) 2025 Fossorial, Inc. + * All rights reserved. + * + * This file is licensed under the Fossorial Commercial License. + * You may not use this file except in compliance with the License. + * Unauthorized use, copying, modification, or distribution is strictly prohibited. + * + * This file is not licensed under the AGPLv3. + */ + +import * as crypto from "crypto"; + +/** + * SSH CA "Server" - Pure TypeScript Implementation + * + * This module provides basic SSH Certificate Authority functionality using + * only Node.js built-in crypto module. No external dependencies or subprocesses. + * + * Usage: + * 1. generateCA() - Creates a new CA key pair, returns CA info including the + * TrustedUserCAKeys line to add to servers + * 2. signPublicKey() - Signs a user's public key with the CA, returns a certificate + */ + +// ============================================================================ +// SSH Wire Format Helpers +// ============================================================================ + +/** + * Encode a string in SSH wire format (4-byte length prefix + data) + */ +function encodeString(data: Buffer | string): Buffer { + const buf = typeof data === "string" ? Buffer.from(data, "utf8") : data; + const len = Buffer.alloc(4); + len.writeUInt32BE(buf.length, 0); + return Buffer.concat([len, buf]); +} + +/** + * Encode a uint32 in SSH wire format (big-endian) + */ +function encodeUInt32(value: number): Buffer { + const buf = Buffer.alloc(4); + buf.writeUInt32BE(value, 0); + return buf; +} + +/** + * Encode a uint64 in SSH wire format (big-endian) + */ +function encodeUInt64(value: bigint): Buffer { + const buf = Buffer.alloc(8); + buf.writeBigUInt64BE(value, 0); + return buf; +} + +/** + * Decode a string from SSH wire format at the given offset + * Returns the string buffer and the new offset + */ +function decodeString(data: Buffer, offset: number): { value: Buffer; newOffset: number } { + const len = data.readUInt32BE(offset); + const value = data.subarray(offset + 4, offset + 4 + len); + return { value, newOffset: offset + 4 + len }; +} + +// ============================================================================ +// SSH Public Key Parsing/Encoding +// ============================================================================ + +/** + * Parse an OpenSSH public key line (e.g., "ssh-ed25519 AAAA... comment") + */ +function parseOpenSSHPublicKey(pubKeyLine: string): { + keyType: string; + keyData: Buffer; + comment: string; +} { + const parts = pubKeyLine.trim().split(/\s+/); + if (parts.length < 2) { + throw new Error("Invalid public key format"); + } + + const keyType = parts[0]; + const keyData = Buffer.from(parts[1], "base64"); + const comment = parts.slice(2).join(" ") || ""; + + // Verify the key type in the blob matches + const { value: blobKeyType } = decodeString(keyData, 0); + if (blobKeyType.toString("utf8") !== keyType) { + throw new Error(`Key type mismatch: ${blobKeyType.toString("utf8")} vs ${keyType}`); + } + + return { keyType, keyData, comment }; +} + +/** + * Encode an Ed25519 public key in OpenSSH format + */ +function encodeEd25519PublicKey(publicKey: Buffer): Buffer { + return Buffer.concat([ + encodeString("ssh-ed25519"), + encodeString(publicKey) + ]); +} + +/** + * Format a public key blob as an OpenSSH public key line + */ +function formatOpenSSHPublicKey(keyBlob: Buffer, comment: string = ""): string { + const { value: keyType } = decodeString(keyBlob, 0); + const base64 = keyBlob.toString("base64"); + return `${keyType.toString("utf8")} ${base64}${comment ? " " + comment : ""}`; +} + +// ============================================================================ +// SSH Certificate Building +// ============================================================================ + +interface CertificateOptions { + /** Serial number for the certificate */ + serial?: bigint; + /** Certificate type: 1 = user, 2 = host */ + certType?: number; + /** Key ID (usually username or identifier) */ + keyId: string; + /** List of valid principals (usernames the cert is valid for) */ + validPrincipals: string[]; + /** Valid after timestamp (seconds since epoch) */ + validAfter?: bigint; + /** Valid before timestamp (seconds since epoch) */ + validBefore?: bigint; + /** Critical options (usually empty for user certs) */ + criticalOptions?: Map; + /** Extensions to enable */ + extensions?: string[]; +} + +/** + * Build the extensions section of the certificate + */ +function buildExtensions(extensions: string[]): Buffer { + // Extensions are a series of name-value pairs, sorted by name + // For boolean extensions, the value is empty + const sortedExtensions = [...extensions].sort(); + + const parts: Buffer[] = []; + for (const ext of sortedExtensions) { + parts.push(encodeString(ext)); + parts.push(encodeString("")); // Empty value for boolean extensions + } + + return encodeString(Buffer.concat(parts)); +} + +/** + * Build the critical options section + */ +function buildCriticalOptions(options: Map): Buffer { + const sortedKeys = [...options.keys()].sort(); + + const parts: Buffer[] = []; + for (const key of sortedKeys) { + parts.push(encodeString(key)); + parts.push(encodeString(encodeString(options.get(key)!))); + } + + return encodeString(Buffer.concat(parts)); +} + +/** + * Build the valid principals section + */ +function buildPrincipals(principals: string[]): Buffer { + const parts: Buffer[] = []; + for (const principal of principals) { + parts.push(encodeString(principal)); + } + return encodeString(Buffer.concat(parts)); +} + +/** + * Extract the raw Ed25519 public key from an OpenSSH public key blob + */ +function extractEd25519PublicKey(keyBlob: Buffer): Buffer { + const { newOffset } = decodeString(keyBlob, 0); // Skip key type + const { value: publicKey } = decodeString(keyBlob, newOffset); + return publicKey; +} + +// ============================================================================ +// CA Interface +// ============================================================================ + +export interface CAKeyPair { + /** CA private key in PEM format (keep this secret!) */ + privateKeyPem: string; + /** CA public key in PEM format */ + publicKeyPem: string; + /** CA public key in OpenSSH format (for TrustedUserCAKeys) */ + publicKeyOpenSSH: string; + /** Raw CA public key bytes (Ed25519) */ + publicKeyRaw: Buffer; +} + +export interface SignedCertificate { + /** The certificate in OpenSSH format (save as id_ed25519-cert.pub or similar) */ + certificate: string; + /** The certificate type string */ + certType: string; + /** Serial number */ + serial: bigint; + /** Key ID */ + keyId: string; + /** Valid principals */ + validPrincipals: string[]; + /** Valid from timestamp */ + validAfter: Date; + /** Valid until timestamp */ + validBefore: Date; +} + +// ============================================================================ +// Main Functions +// ============================================================================ + +/** + * Generate a new SSH Certificate Authority key pair. + * + * Returns the CA keys and the line to add to /etc/ssh/sshd_config: + * TrustedUserCAKeys /etc/ssh/ca.pub + * + * Then save the publicKeyOpenSSH to /etc/ssh/ca.pub on the server. + * + * @param comment - Optional comment for the CA public key + * @returns CA key pair and configuration info + */ +export function generateCA(comment: string = "ssh-ca"): CAKeyPair { + // Generate Ed25519 key pair + const { publicKey, privateKey } = crypto.generateKeyPairSync("ed25519", { + publicKeyEncoding: { type: "spki", format: "pem" }, + privateKeyEncoding: { type: "pkcs8", format: "pem" } + }); + + // Get raw public key bytes + const pubKeyObj = crypto.createPublicKey(publicKey); + const rawPubKey = pubKeyObj.export({ type: "spki", format: "der" }); + // Ed25519 SPKI format: 12 byte header + 32 byte key + const ed25519PubKey = rawPubKey.subarray(rawPubKey.length - 32); + + // Create OpenSSH format public key + const pubKeyBlob = encodeEd25519PublicKey(ed25519PubKey); + const publicKeyOpenSSH = formatOpenSSHPublicKey(pubKeyBlob, comment); + + return { + privateKeyPem: privateKey, + publicKeyPem: publicKey, + publicKeyOpenSSH, + publicKeyRaw: ed25519PubKey + }; +} + +// ============================================================================ +// Helper Functions +// ============================================================================ + +/** + * Get and decrypt the SSH CA keys for an organization. + * + * @param orgId - Organization ID + * @param decryptionKey - Key to decrypt the CA private key (typically server.secret from config) + * @returns CA key pair or null if not found + */ +export async function getOrgCAKeys( + orgId: string, + decryptionKey: string +): Promise { + const { db, orgs } = await import("@server/db"); + const { eq } = await import("drizzle-orm"); + const { decrypt } = await import("@server/lib/crypto"); + + const [org] = await db + .select({ + sshCaPrivateKey: orgs.sshCaPrivateKey, + sshCaPublicKey: orgs.sshCaPublicKey + }) + .from(orgs) + .where(eq(orgs.orgId, orgId)) + .limit(1); + + if (!org || !org.sshCaPrivateKey || !org.sshCaPublicKey) { + return null; + } + + const privateKeyPem = decrypt(org.sshCaPrivateKey, decryptionKey); + + // Extract raw public key from the OpenSSH format + const { keyData } = parseOpenSSHPublicKey(org.sshCaPublicKey); + const { newOffset } = decodeString(keyData, 0); // Skip key type + const { value: publicKeyRaw } = decodeString(keyData, newOffset); + + // Get PEM format of public key + const pubKeyObj = crypto.createPublicKey({ + key: privateKeyPem, + format: "pem" + }); + const publicKeyPem = pubKeyObj.export({ type: "spki", format: "pem" }) as string; + + return { + privateKeyPem, + publicKeyPem, + publicKeyOpenSSH: org.sshCaPublicKey, + publicKeyRaw + }; +} + +/** + * Sign a user's SSH public key with the CA, producing a certificate. + * + * The resulting certificate should be saved alongside the user's private key + * with a -cert.pub suffix. For example: + * - Private key: ~/.ssh/id_ed25519 + * - Certificate: ~/.ssh/id_ed25519-cert.pub + * + * @param caPrivateKeyPem - CA private key in PEM format + * @param userPublicKeyLine - User's public key in OpenSSH format + * @param options - Certificate options (principals, validity, etc.) + * @returns Signed certificate + */ +export function signPublicKey( + caPrivateKeyPem: string, + userPublicKeyLine: string, + options: CertificateOptions +): SignedCertificate { + // Parse the user's public key + const { keyType, keyData } = parseOpenSSHPublicKey(userPublicKeyLine); + + // Determine certificate type string + let certTypeString: string; + if (keyType === "ssh-ed25519") { + certTypeString = "ssh-ed25519-cert-v01@openssh.com"; + } else if (keyType === "ssh-rsa") { + certTypeString = "ssh-rsa-cert-v01@openssh.com"; + } else if (keyType === "ecdsa-sha2-nistp256") { + certTypeString = "ecdsa-sha2-nistp256-cert-v01@openssh.com"; + } else if (keyType === "ecdsa-sha2-nistp384") { + certTypeString = "ecdsa-sha2-nistp384-cert-v01@openssh.com"; + } else if (keyType === "ecdsa-sha2-nistp521") { + certTypeString = "ecdsa-sha2-nistp521-cert-v01@openssh.com"; + } else { + throw new Error(`Unsupported key type: ${keyType}`); + } + + // Get CA public key from private key + const caPrivKey = crypto.createPrivateKey(caPrivateKeyPem); + const caPubKey = crypto.createPublicKey(caPrivKey); + const caRawPubKey = caPubKey.export({ type: "spki", format: "der" }); + const caEd25519PubKey = caRawPubKey.subarray(caRawPubKey.length - 32); + const caPubKeyBlob = encodeEd25519PublicKey(caEd25519PubKey); + + // Set defaults + const serial = options.serial ?? BigInt(Date.now()); + const certType = options.certType ?? 1; // 1 = user cert + const now = BigInt(Math.floor(Date.now() / 1000)); + const validAfter = options.validAfter ?? (now - 60n); // 1 minute ago + const validBefore = options.validBefore ?? (now + 86400n * 365n); // 1 year from now + + // Default extensions for user certificates + const defaultExtensions = [ + "permit-X11-forwarding", + "permit-agent-forwarding", + "permit-port-forwarding", + "permit-pty", + "permit-user-rc" + ]; + const extensions = options.extensions ?? defaultExtensions; + const criticalOptions = options.criticalOptions ?? new Map(); + + // Generate nonce (random bytes) + const nonce = crypto.randomBytes(32); + + // Extract the public key portion from the user's key blob + // For Ed25519: skip the key type string, get the public key (already encoded) + let userKeyPortion: Buffer; + if (keyType === "ssh-ed25519") { + // Skip the key type string, take the rest (which is encodeString(32-byte-key)) + const { newOffset } = decodeString(keyData, 0); + userKeyPortion = keyData.subarray(newOffset); + } else { + // For other key types, extract everything after the key type + const { newOffset } = decodeString(keyData, 0); + userKeyPortion = keyData.subarray(newOffset); + } + + // Build the certificate body (to be signed) + const certBody = Buffer.concat([ + encodeString(certTypeString), + encodeString(nonce), + userKeyPortion, + encodeUInt64(serial), + encodeUInt32(certType), + encodeString(options.keyId), + buildPrincipals(options.validPrincipals), + encodeUInt64(validAfter), + encodeUInt64(validBefore), + buildCriticalOptions(criticalOptions), + buildExtensions(extensions), + encodeString(""), // reserved + encodeString(caPubKeyBlob) // signature key (CA public key) + ]); + + // Sign the certificate body + const signature = crypto.sign(null, certBody, caPrivKey); + + // Build the full signature blob (algorithm + signature) + const signatureBlob = Buffer.concat([ + encodeString("ssh-ed25519"), + encodeString(signature) + ]); + + // Build complete certificate + const certificate = Buffer.concat([ + certBody, + encodeString(signatureBlob) + ]); + + // Format as OpenSSH certificate line + const certLine = `${certTypeString} ${certificate.toString("base64")} ${options.keyId}`; + + return { + certificate: certLine, + certType: certTypeString, + serial, + keyId: options.keyId, + validPrincipals: options.validPrincipals, + validAfter: new Date(Number(validAfter) * 1000), + validBefore: new Date(Number(validBefore) * 1000) + }; +} diff --git a/server/private/routers/external.ts b/server/private/routers/external.ts index dae10a95..17132c44 100644 --- a/server/private/routers/external.ts +++ b/server/private/routers/external.ts @@ -25,6 +25,7 @@ import * as logs from "#private/routers/auditLogs"; import * as misc from "#private/routers/misc"; import * as reKey from "#private/routers/re-key"; import * as approval from "#private/routers/approvals"; +import * as ssh from "#private/routers/ssh"; import { verifyOrgAccess, @@ -506,3 +507,14 @@ authenticated.put( verifyUserHasAction(ActionsEnum.reGenerateSecret), reKey.reGenerateExitNodeSecret ); + +authenticated.post( + "/org/:orgId/ssh/sign-key", + verifyValidLicense, + verifyValidSubscription(tierMatrix.sshPam), + verifyOrgAccess, + verifyLimits, + // verifyUserHasAction(ActionsEnum.signSshKey), + logActionAudit(ActionsEnum.signSshKey), + ssh.signSshKey +); diff --git a/server/private/routers/ssh/index.ts b/server/private/routers/ssh/index.ts new file mode 100644 index 00000000..a98405ba --- /dev/null +++ b/server/private/routers/ssh/index.ts @@ -0,0 +1,14 @@ +/* + * This file is part of a proprietary work. + * + * Copyright (c) 2025 Fossorial, Inc. + * All rights reserved. + * + * This file is licensed under the Fossorial Commercial License. + * You may not use this file except in compliance with the License. + * Unauthorized use, copying, modification, or distribution is strictly prohibited. + * + * This file is not licensed under the AGPLv3. + */ + +export * from "./signSshKey"; \ No newline at end of file diff --git a/server/private/routers/ssh/signSshKey.ts b/server/private/routers/ssh/signSshKey.ts new file mode 100644 index 00000000..62a830cb --- /dev/null +++ b/server/private/routers/ssh/signSshKey.ts @@ -0,0 +1,265 @@ +/* + * This file is part of a proprietary work. + * + * Copyright (c) 2025 Fossorial, Inc. + * All rights reserved. + * + * This file is licensed under the Fossorial Commercial License. + * You may not use this file except in compliance with the License. + * Unauthorized use, copying, modification, or distribution is strictly prohibited. + * + * This file is not licensed under the AGPLv3. + */ + +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import { db, orgs, siteResources } from "@server/db"; +import response from "@server/lib/response"; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import logger from "@server/logger"; +import { fromError } from "zod-validation-error"; +import { OpenAPITags, registry } from "@server/openApi"; +import { eq, or } from "drizzle-orm"; +import { canUserAccessSiteResource } from "@server/auth/canUserAccessSiteResource"; +import { signPublicKey, getOrgCAKeys } from "#private/lib/sshCA"; +import config from "@server/lib/config"; + +const paramsSchema = z.strictObject({ + orgId: z.string().nonempty() +}); + +const bodySchema = z + .strictObject({ + publicKey: z.string().nonempty(), + resourceId: z.number().int().positive().optional(), + niceId: z.string().nonempty().optional(), + alias: z.string().nonempty().optional() + }) + .refine( + (data) => { + const fields = [data.resourceId, data.niceId, data.alias]; + const definedFields = fields.filter((field) => field !== undefined); + return definedFields.length === 1; + }, + { + message: + "Exactly one of resourceId, niceId, or alias must be provided" + } + ); + +export type SignSshKeyResponse = { + certificate: string; + sshUsername: string; + sshHost: string; + resourceId: number; + keyId: string; + validPrincipals: string[]; + validAfter: string; + validBefore: string; + expiresIn: number; +}; + +// registry.registerPath({ +// method: "post", +// path: "/org/{orgId}/ssh/sign-key", +// description: "Sign an SSH public key for access to a resource.", +// tags: [OpenAPITags.Org, OpenAPITags.Ssh], +// request: { +// params: paramsSchema, +// body: { +// content: { +// "application/json": { +// schema: bodySchema +// } +// } +// } +// }, +// responses: {} +// }); + +export async function signSshKey( + req: Request, + res: Response, + next: NextFunction +): Promise { + try { + const parsedParams = paramsSchema.safeParse(req.params); + if (!parsedParams.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + fromError(parsedParams.error).toString() + ) + ); + } + + const parsedBody = bodySchema.safeParse(req.body); + if (!parsedBody.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + fromError(parsedBody.error).toString() + ) + ); + } + + const { orgId } = parsedParams.data; + const { publicKey, resourceId, niceId, alias } = parsedBody.data; + const userId = req.user?.userId; + const roleId = req.userOrgRoleId!; + + if (!userId) { + return next( + createHttpError(HttpCode.UNAUTHORIZED, "User not authenticated") + ); + } + + // Get and decrypt the org's CA keys + const caKeys = await getOrgCAKeys( + orgId, + config.getRawConfig().server.secret! + ); + + if (!caKeys) { + return next( + createHttpError( + HttpCode.NOT_FOUND, + "SSH CA not configured for this organization" + ) + ); + } + + // Verify the resource exists and belongs to the org + // Build the where clause dynamically based on which field is provided + let whereClause; + if (resourceId !== undefined) { + whereClause = eq(siteResources.siteResourceId, resourceId); + } else if (niceId !== undefined) { + whereClause = eq(siteResources.niceId, niceId); + } else if (alias !== undefined) { + whereClause = eq(siteResources.alias, alias); + } else { + // This should never happen due to the schema validation, but TypeScript doesn't know that + return next( + createHttpError( + HttpCode.BAD_REQUEST, + "One of resourceId, niceId, or alias must be provided" + ) + ); + } + + const [resource] = await db + .select() + .from(siteResources) + .where(whereClause) + .limit(1); + + if (!resource) { + return next( + createHttpError( + HttpCode.NOT_FOUND, + `Resource not found` + ) + ); + } + + if (resource.orgId !== orgId) { + return next( + createHttpError( + HttpCode.FORBIDDEN, + "Resource does not belong to the specified organization" + ) + ); + } + + // Check if the user has access to the resource + const hasAccess = await canUserAccessSiteResource({ + userId: userId, + resourceId: resource.siteResourceId, + roleId: roleId + }); + + if (!hasAccess) { + return next( + createHttpError( + HttpCode.FORBIDDEN, + "User does not have access to this resource" + ) + ); + } + + let usernameToUse; + if (req.user?.email) { + // Extract username from email (first part before @) + usernameToUse = req.user?.email.split("@")[0]; + if (!usernameToUse) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + "Unable to extract username from email" + ) + ); + } + } else if (req.user?.username) { + usernameToUse = req.user.username; + // We need to clean out any spaces or special characters from the username to ensure it's valid for SSH certificates + usernameToUse = usernameToUse.replace(/[^a-zA-Z0-9_-]/g, ""); + if (!usernameToUse) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + "Username is not valid for SSH certificate" + ) + ); + } + } else { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + "User does not have a valid email or username for SSH certificate" + ) + ); + } + + // Sign the public key + const now = BigInt(Math.floor(Date.now() / 1000)); + // only valid for 5 minutes + const validFor = 300n; + + const cert = signPublicKey(caKeys.privateKeyPem, publicKey, { + keyId: `${usernameToUse}@${orgId}`, + validPrincipals: [usernameToUse, resource.niceId], + validAfter: now - 60n, // Start 1 min ago for clock skew + validBefore: now + validFor + }); + + const expiresIn = Number(validFor); // seconds + + return response(res, { + data: { + certificate: cert.certificate, + sshUsername: usernameToUse, + sshHost: resource.niceId, + resourceId: resource.siteResourceId, + keyId: cert.keyId, + validPrincipals: cert.validPrincipals, + validAfter: cert.validAfter.toISOString(), + validBefore: cert.validBefore.toISOString(), + expiresIn + }, + success: true, + error: false, + message: "SSH key signed successfully", + status: HttpCode.OK + }); + } catch (error) { + logger.error("Error signing SSH key:", error); + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "An error occurred while signing the SSH key" + ) + ); + } +} diff --git a/server/routers/org/createOrg.ts b/server/routers/org/createOrg.ts index 29468ca1..b8e2d625 100644 --- a/server/routers/org/createOrg.ts +++ b/server/routers/org/createOrg.ts @@ -28,6 +28,8 @@ import { FeatureId } from "@server/lib/billing"; import { build } from "@server/build"; import { calculateUserClientsForOrgs } from "@server/lib/calculateUserClientsForOrgs"; import { doCidrsOverlap } from "@server/lib/ip"; +import { generateCA } from "@server/private/lib/sshCA"; +import { encrypt } from "@server/lib/crypto"; const createOrgSchema = z.strictObject({ orgId: z.string(), @@ -143,6 +145,11 @@ export async function createOrg( .from(domains) .where(eq(domains.configManaged, true)); + // Generate SSH CA keys for the org + const ca = generateCA(`${orgId}-ca`); + const encryptionKey = config.getRawConfig().server.secret!; + const encryptedCaPrivateKey = encrypt(ca.privateKeyPem, encryptionKey); + const newOrg = await trx .insert(orgs) .values({ @@ -150,7 +157,9 @@ export async function createOrg( name, subnet, utilitySubnet, - createdAt: new Date().toISOString() + createdAt: new Date().toISOString(), + sshCaPrivateKey: encryptedCaPrivateKey, + sshCaPublicKey: ca.publicKeyOpenSSH }) .returning(); From f0b92405755da64e9837aaea1781ee8f942d342d Mon Sep 17 00:00:00 2001 From: Owen Date: Mon, 16 Feb 2026 15:29:23 -0800 Subject: [PATCH 61/81] Accept resource as either niceId or alias --- server/private/routers/ssh/signSshKey.ts | 42 ++++++++++++------- server/routers/resource/updateResource.ts | 2 +- .../siteResource/updateSiteResource.ts | 1 + 3 files changed, 29 insertions(+), 16 deletions(-) diff --git a/server/private/routers/ssh/signSshKey.ts b/server/private/routers/ssh/signSshKey.ts index 62a830cb..d2f4bcc7 100644 --- a/server/private/routers/ssh/signSshKey.ts +++ b/server/private/routers/ssh/signSshKey.ts @@ -20,7 +20,7 @@ import createHttpError from "http-errors"; import logger from "@server/logger"; import { fromError } from "zod-validation-error"; import { OpenAPITags, registry } from "@server/openApi"; -import { eq, or } from "drizzle-orm"; +import { eq, or, and } from "drizzle-orm"; import { canUserAccessSiteResource } from "@server/auth/canUserAccessSiteResource"; import { signPublicKey, getOrgCAKeys } from "#private/lib/sshCA"; import config from "@server/lib/config"; @@ -33,12 +33,11 @@ const bodySchema = z .strictObject({ publicKey: z.string().nonempty(), resourceId: z.number().int().positive().optional(), - niceId: z.string().nonempty().optional(), - alias: z.string().nonempty().optional() + resource: z.string().nonempty().optional() // this is either the nice id or the alias }) .refine( (data) => { - const fields = [data.resourceId, data.niceId, data.alias]; + const fields = [data.resourceId, data.resource]; const definedFields = fields.filter((field) => field !== undefined); return definedFields.length === 1; }, @@ -105,7 +104,11 @@ export async function signSshKey( } const { orgId } = parsedParams.data; - const { publicKey, resourceId, niceId, alias } = parsedBody.data; + const { + publicKey, + resourceId, + resource: resourceQueryString + } = parsedBody.data; const userId = req.user?.userId; const roleId = req.userOrgRoleId!; @@ -135,10 +138,11 @@ export async function signSshKey( let whereClause; if (resourceId !== undefined) { whereClause = eq(siteResources.siteResourceId, resourceId); - } else if (niceId !== undefined) { - whereClause = eq(siteResources.niceId, niceId); - } else if (alias !== undefined) { - whereClause = eq(siteResources.alias, alias); + } else if (resourceQueryString !== undefined) { + whereClause = or( + eq(siteResources.niceId, resourceQueryString), + eq(siteResources.alias, resourceQueryString) + ); } else { // This should never happen due to the schema validation, but TypeScript doesn't know that return next( @@ -149,21 +153,29 @@ export async function signSshKey( ); } - const [resource] = await db + const resources = await db .select() .from(siteResources) - .where(whereClause) - .limit(1); + .where(and(whereClause, eq(siteResources.orgId, orgId))); - if (!resource) { + if (!resources || resources.length === 0) { + return next( + createHttpError(HttpCode.NOT_FOUND, `Resource not found`) + ); + } + + if (resources.length > 1) { + // error but this should not happen because the nice id cant contain a dot and the alias has to have a dot and both have to be unique within the org so there should never be multiple matches return next( createHttpError( - HttpCode.NOT_FOUND, - `Resource not found` + HttpCode.BAD_REQUEST, + `Multiple resources found matching the criteria` ) ); } + const resource = resources[0]; + if (resource.orgId !== orgId) { return next( createHttpError( diff --git a/server/routers/resource/updateResource.ts b/server/routers/resource/updateResource.ts index 84b4f538..4f35739b 100644 --- a/server/routers/resource/updateResource.ts +++ b/server/routers/resource/updateResource.ts @@ -33,7 +33,7 @@ const updateResourceParamsSchema = z.strictObject({ const updateHttpResourceBodySchema = z .strictObject({ name: z.string().min(1).max(255).optional(), - niceId: z.string().min(1).max(255).optional(), + niceId: z.string().min(1).max(255).regex(/^[a-zA-Z0-9-]+$/, "niceId can only contain letters, numbers, and dashes").optional(), subdomain: subdomainSchema.nullable().optional(), ssl: z.boolean().optional(), sso: z.boolean().optional(), diff --git a/server/routers/siteResource/updateSiteResource.ts b/server/routers/siteResource/updateSiteResource.ts index fd77c74a..4c19bea1 100644 --- a/server/routers/siteResource/updateSiteResource.ts +++ b/server/routers/siteResource/updateSiteResource.ts @@ -41,6 +41,7 @@ const updateSiteResourceSchema = z .strictObject({ name: z.string().min(1).max(255).optional(), siteId: z.int(), + // niceId: z.string().min(1).max(255).regex(/^[a-zA-Z0-9-]+$/, "niceId can only contain letters, numbers, and dashes").optional(), // mode: z.enum(["host", "cidr", "port"]).optional(), mode: z.enum(["host", "cidr"]).optional(), // protocol: z.enum(["tcp", "udp"]).nullish(), From 5092eb58fbbe41bc6d87f7c6a44759c9d73bd07e Mon Sep 17 00:00:00 2001 From: Owen Date: Mon, 16 Feb 2026 15:31:09 -0800 Subject: [PATCH 62/81] Ssh host should be the destination --- server/private/routers/ssh/signSshKey.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/private/routers/ssh/signSshKey.ts b/server/private/routers/ssh/signSshKey.ts index d2f4bcc7..593a83bb 100644 --- a/server/private/routers/ssh/signSshKey.ts +++ b/server/private/routers/ssh/signSshKey.ts @@ -252,7 +252,7 @@ export async function signSshKey( data: { certificate: cert.certificate, sshUsername: usernameToUse, - sshHost: resource.niceId, + sshHost: resource.destination, resourceId: resource.siteResourceId, keyId: cert.keyId, validPrincipals: cert.validPrincipals, From 3debc6c8d3ba00ed92da296daa854409af125db8 Mon Sep 17 00:00:00 2001 From: Owen Date: Mon, 16 Feb 2026 20:29:55 -0800 Subject: [PATCH 63/81] Add round trip tracking for any message --- server/db/pg/schema/schema.ts | 14 +- server/db/sqlite/schema/schema.ts | 16 +- server/private/routers/ssh/signSshKey.ts | 177 ++++++++++++++++---- server/routers/external.ts | 3 + server/routers/ws/checkRoundTripMessage.ts | 85 ++++++++++ server/routers/ws/handleRoundTripMessage.ts | 49 ++++++ server/routers/ws/index.ts | 1 + server/routers/ws/messageHandlers.ts | 4 +- 8 files changed, 317 insertions(+), 32 deletions(-) create mode 100644 server/routers/ws/checkRoundTripMessage.ts create mode 100644 server/routers/ws/handleRoundTripMessage.ts diff --git a/server/db/pg/schema/schema.ts b/server/db/pg/schema/schema.ts index 4188d894..ca46e207 100644 --- a/server/db/pg/schema/schema.ts +++ b/server/db/pg/schema/schema.ts @@ -330,7 +330,8 @@ export const userOrgs = pgTable("userOrgs", { .notNull() .references(() => roles.roleId), isOwner: boolean("isOwner").notNull().default(false), - autoProvisioned: boolean("autoProvisioned").default(false) + autoProvisioned: boolean("autoProvisioned").default(false), + pamUsername: varchar("pamUsername") // cleaned username for ssh and such }); export const emailVerificationCodes = pgTable("emailVerificationCodes", { @@ -986,6 +987,16 @@ export const deviceWebAuthCodes = pgTable("deviceWebAuthCodes", { }) }); +export const roundTripMessageTracker = pgTable("roundTripMessageTracker", { + messageId: serial("messageId").primaryKey(), + wsClientId: varchar("clientId"), + messageType: varchar("messageType"), + sentAt: bigint("sentAt", { mode: "number" }).notNull(), + receivedAt: bigint("receivedAt", { mode: "number" }), + error: text("error"), + complete: boolean("complete").notNull().default(false) +}); + export type Org = InferSelectModel; export type User = InferSelectModel; export type Site = InferSelectModel; @@ -1046,3 +1057,4 @@ export type SecurityKey = InferSelectModel; export type WebauthnChallenge = InferSelectModel; export type DeviceWebAuthCode = InferSelectModel; export type RequestAuditLog = InferSelectModel; +export type RoundTripMessageTracker = InferSelectModel; diff --git a/server/db/sqlite/schema/schema.ts b/server/db/sqlite/schema/schema.ts index 6d60ec68..ce08dea1 100644 --- a/server/db/sqlite/schema/schema.ts +++ b/server/db/sqlite/schema/schema.ts @@ -635,7 +635,8 @@ export const userOrgs = sqliteTable("userOrgs", { isOwner: integer("isOwner", { mode: "boolean" }).notNull().default(false), autoProvisioned: integer("autoProvisioned", { mode: "boolean" - }).default(false) + }).default(false), + pamUsername: text("pamUsername") // cleaned username for ssh and such }); export const emailVerificationCodes = sqliteTable("emailVerificationCodes", { @@ -1077,6 +1078,16 @@ export const deviceWebAuthCodes = sqliteTable("deviceWebAuthCodes", { }) }); +export const roundTripMessageTracker = sqliteTable("roundTripMessageTracker", { + messageId: integer("messageId").primaryKey({ autoIncrement: true }), + wsClientId: text("clientId"), + messageType: text("messageType"), + sentAt: integer("sentAt").notNull(), + receivedAt: integer("receivedAt"), + error: text("error"), + complete: integer("complete", { mode: "boolean" }).notNull().default(false) +}); + export type Org = InferSelectModel; export type User = InferSelectModel; export type Site = InferSelectModel; @@ -1138,3 +1149,6 @@ export type SecurityKey = InferSelectModel; export type WebauthnChallenge = InferSelectModel; export type RequestAuditLog = InferSelectModel; export type DeviceWebAuthCode = InferSelectModel; +export type RoundTripMessageTracker = InferSelectModel< + typeof roundTripMessageTracker +>; diff --git a/server/private/routers/ssh/signSshKey.ts b/server/private/routers/ssh/signSshKey.ts index 593a83bb..378c3576 100644 --- a/server/private/routers/ssh/signSshKey.ts +++ b/server/private/routers/ssh/signSshKey.ts @@ -13,7 +13,7 @@ import { Request, Response, NextFunction } from "express"; import { z } from "zod"; -import { db, orgs, siteResources } from "@server/db"; +import { db, newts, orgs, roundTripMessageTracker, siteResources, sites, userOrgs } from "@server/db"; import response from "@server/lib/response"; import HttpCode from "@server/types/HttpCode"; import createHttpError from "http-errors"; @@ -24,6 +24,7 @@ import { eq, or, and } from "drizzle-orm"; import { canUserAccessSiteResource } from "@server/auth/canUserAccessSiteResource"; import { signPublicKey, getOrgCAKeys } from "#private/lib/sshCA"; import config from "@server/lib/config"; +import { sendToClient } from "#dynamic/routers/ws"; const paramsSchema = z.strictObject({ orgId: z.string().nonempty() @@ -49,6 +50,7 @@ const bodySchema = z export type SignSshKeyResponse = { certificate: string; + messageId: number; sshUsername: string; sshHost: string; resourceId: number; @@ -118,6 +120,104 @@ export async function signSshKey( ); } + const [userOrg] = await db + .select() + .from(userOrgs) + .where(and(eq(userOrgs.orgId, orgId), eq(userOrgs.userId, userId))) + .limit(1); + + if (!userOrg) { + return next( + createHttpError( + HttpCode.FORBIDDEN, + "User does not belong to the specified organization" + ) + ); + } + + let usernameToUse; + if (!userOrg.pamUsername) { + if (req.user?.email) { + // Extract username from email (first part before @) + usernameToUse = req.user?.email.split("@")[0]; + if (!usernameToUse) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + "Unable to extract username from email" + ) + ); + } + } else if (req.user?.username) { + usernameToUse = req.user.username; + // We need to clean out any spaces or special characters from the username to ensure it's valid for SSH certificates + usernameToUse = usernameToUse.replace(/[^a-zA-Z0-9_-]/g, ""); + if (!usernameToUse) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + "Username is not valid for SSH certificate" + ) + ); + } + } else { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + "User does not have a valid email or username for SSH certificate" + ) + ); + } + + // check if we have a existing user in this org with the same + const [existingUserWithSameName] = await db + .select() + .from(userOrgs) + .where( + and( + eq(userOrgs.orgId, orgId), + eq(userOrgs.pamUsername, usernameToUse) + ) + ) + .limit(1); + + if (existingUserWithSameName) { + let foundUniqueUsername = false; + for (let attempt = 0; attempt < 20; attempt++) { + const randomNum = Math.floor(Math.random() * 101); // 0 to 100 + const candidateUsername = `${usernameToUse}${randomNum}`; + + const [existingUser] = await db + .select() + .from(userOrgs) + .where( + and( + eq(userOrgs.orgId, orgId), + eq(userOrgs.pamUsername, candidateUsername) + ) + ) + .limit(1); + + if (!existingUser) { + usernameToUse = candidateUsername; + foundUniqueUsername = true; + break; + } + } + + if (!foundUniqueUsername) { + return next( + createHttpError( + HttpCode.CONFLICT, + "Unable to generate a unique username for SSH certificate" + ) + ); + } + } + } else { + usernameToUse = userOrg.pamUsername; + } + // Get and decrypt the org's CA keys const caKeys = await getOrgCAKeys( orgId, @@ -201,35 +301,18 @@ export async function signSshKey( ); } - let usernameToUse; - if (req.user?.email) { - // Extract username from email (first part before @) - usernameToUse = req.user?.email.split("@")[0]; - if (!usernameToUse) { - return next( - createHttpError( - HttpCode.BAD_REQUEST, - "Unable to extract username from email" - ) - ); - } - } else if (req.user?.username) { - usernameToUse = req.user.username; - // We need to clean out any spaces or special characters from the username to ensure it's valid for SSH certificates - usernameToUse = usernameToUse.replace(/[^a-zA-Z0-9_-]/g, ""); - if (!usernameToUse) { - return next( - createHttpError( - HttpCode.BAD_REQUEST, - "Username is not valid for SSH certificate" - ) - ); - } - } else { + // get the site + const [newt] = await db + .select() + .from(newts) + .where(eq(newts.siteId, resource.siteId)) + .limit(1); + + if (!newt) { return next( createHttpError( - HttpCode.BAD_REQUEST, - "User does not have a valid email or username for SSH certificate" + HttpCode.INTERNAL_SERVER_ERROR, + "Site associated with resource not found" ) ); } @@ -240,17 +323,53 @@ export async function signSshKey( const validFor = 300n; const cert = signPublicKey(caKeys.privateKeyPem, publicKey, { - keyId: `${usernameToUse}@${orgId}`, + keyId: `${usernameToUse}@${resource.niceId}`, validPrincipals: [usernameToUse, resource.niceId], validAfter: now - 60n, // Start 1 min ago for clock skew validBefore: now + validFor }); + const [message] = await db + .insert(roundTripMessageTracker) + .values({ + wsClientId: newt.newtId, + messageType: `newt/pam/connection`, + sentAt: Math.floor(Date.now() / 1000), + }) + .returning(); + + if (!message) { + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "Failed to create message tracker entry" + ) + ); + } + + await sendToClient(newt.newtId, { + type: `newt/pam/connection`, + data: { + messageId: message.messageId, + orgId: orgId, + agentPort: 8080, + agentHost: resource.destination, + caCert: publicKey, + username: usernameToUse, + niceId: resource.niceId, + metadata: { + sudo: true, + homedir: true + } + } + }); + const expiresIn = Number(validFor); // seconds return response(res, { data: { certificate: cert.certificate, + messageId: message.messageId, sshUsername: usernameToUse, sshHost: resource.destination, resourceId: resource.siteResourceId, diff --git a/server/routers/external.ts b/server/routers/external.ts index 5d25e898..a9d075a6 100644 --- a/server/routers/external.ts +++ b/server/routers/external.ts @@ -50,6 +50,7 @@ import createHttpError from "http-errors"; import { build } from "@server/build"; import { createStore } from "#dynamic/lib/rateLimitStore"; import { logActionAudit } from "#dynamic/middlewares"; +import { checkRoundTripMessage } from "./ws"; // Root routes export const unauthenticated = Router(); @@ -1123,6 +1124,8 @@ authenticated.get( blueprints.getBlueprint ); +authenticated.get("/ws/round-trip-message/:messageId", checkRoundTripMessage); + // Auth routes export const authRouter = Router(); unauthenticated.use("/auth", authRouter); diff --git a/server/routers/ws/checkRoundTripMessage.ts b/server/routers/ws/checkRoundTripMessage.ts new file mode 100644 index 00000000..9c832db5 --- /dev/null +++ b/server/routers/ws/checkRoundTripMessage.ts @@ -0,0 +1,85 @@ +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import { db, roundTripMessageTracker } from "@server/db"; +import response from "@server/lib/response"; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import logger from "@server/logger"; +import { fromError } from "zod-validation-error"; +import { eq } from "drizzle-orm"; +import { OpenAPITags, registry } from "@server/openApi"; + +const checkRoundTripMessageParamsSchema = z + .object({ + messageId: z + .string() + .transform(Number) + .pipe(z.number().int().positive()) + }) + .strict(); + +// registry.registerPath({ +// method: "get", +// path: "/ws/round-trip-message/{messageId}", +// description: +// "Check if a round trip message has been completed by checking the roundTripMessageTracker table", +// tags: [OpenAPITags.WebSocket], +// request: { +// params: checkRoundTripMessageParamsSchema +// }, +// responses: {} +// }); + +export async function checkRoundTripMessage( + req: Request, + res: Response, + next: NextFunction +): Promise { + try { + const parsedParams = checkRoundTripMessageParamsSchema.safeParse( + req.params + ); + if (!parsedParams.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + fromError(parsedParams.error).toString() + ) + ); + } + + const { messageId } = parsedParams.data; + + // Get the round trip message from the tracker + const [message] = await db + .select() + .from(roundTripMessageTracker) + .where(eq(roundTripMessageTracker.messageId, messageId)) + .limit(1); + + if (!message) { + return next( + createHttpError(HttpCode.NOT_FOUND, "Message not found") + ); + } + + return response(res, { + data: { + messageId: message.messageId, + complete: message.complete, + sentAt: message.sentAt, + receivedAt: message.receivedAt, + error: message.error, + }, + success: true, + error: false, + message: "Round trip message status retrieved successfully", + status: HttpCode.OK + }); + } catch (error) { + logger.error(error); + return next( + createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred") + ); + } +} diff --git a/server/routers/ws/handleRoundTripMessage.ts b/server/routers/ws/handleRoundTripMessage.ts new file mode 100644 index 00000000..ed5d0773 --- /dev/null +++ b/server/routers/ws/handleRoundTripMessage.ts @@ -0,0 +1,49 @@ +import { db, roundTripMessageTracker } from "@server/db"; +import { MessageHandler } from "@server/routers/ws"; +import { eq } from "drizzle-orm"; +import logger from "@server/logger"; + +interface RoundTripCompleteMessage { + messageId: number; + complete: boolean; + error?: string; +} + +export const handleRoundTripMessage: MessageHandler = async ( + context +) => { + const { message, client: c } = context; + + logger.info("Handling round trip message"); + + const data = message.data as RoundTripCompleteMessage; + + try { + const { messageId, complete, error } = data; + + if (!messageId) { + logger.error("Round trip message missing messageId"); + return; + } + + // Update the roundTripMessageTracker with completion status + await db + .update(roundTripMessageTracker) + .set({ + complete: complete, + receivedAt: Math.floor(Date.now() / 1000), + error: error || null + }) + .where(eq(roundTripMessageTracker.messageId, messageId)); + + logger.info(`Round trip message ${messageId} marked as complete: ${complete}`); + + if (error) { + logger.warn(`Round trip message ${messageId} completed with error: ${error}`); + } + } catch (error) { + logger.error("Error processing round trip message:", error); + } + + return; +}; diff --git a/server/routers/ws/index.ts b/server/routers/ws/index.ts index b580b369..f5b4e2e4 100644 --- a/server/routers/ws/index.ts +++ b/server/routers/ws/index.ts @@ -1,2 +1,3 @@ export * from "./ws"; export * from "./types"; +export * from "./checkRoundTripMessage"; diff --git a/server/routers/ws/messageHandlers.ts b/server/routers/ws/messageHandlers.ts index 45c62e6c..9a14344a 100644 --- a/server/routers/ws/messageHandlers.ts +++ b/server/routers/ws/messageHandlers.ts @@ -18,6 +18,7 @@ import { handleOlmDisconnecingMessage } from "../olm"; import { handleHealthcheckStatusMessage } from "../target"; +import { handleRoundTripMessage } from "./handleRoundTripMessage"; import { MessageHandler } from "./types"; export const messageHandlers: Record = { @@ -35,7 +36,8 @@ export const messageHandlers: Record = { "newt/socket/containers": handleDockerContainersMessage, "newt/ping/request": handleNewtPingRequestMessage, "newt/blueprint/apply": handleApplyBlueprintMessage, - "newt/healthcheck/status": handleHealthcheckStatusMessage + "newt/healthcheck/status": handleHealthcheckStatusMessage, + "ws/round-trip/complete": handleRoundTripMessage }; startOlmOfflineChecker(); // this is to handle the offline check for olms From d00262dc31be8f03b149c4c44b9d5e15959390ff Mon Sep 17 00:00:00 2001 From: Owen Date: Tue, 17 Feb 2026 11:43:38 -0800 Subject: [PATCH 64/81] Send the right port and cert --- server/private/routers/ssh/signSshKey.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/server/private/routers/ssh/signSshKey.ts b/server/private/routers/ssh/signSshKey.ts index 378c3576..9ffce8c1 100644 --- a/server/private/routers/ssh/signSshKey.ts +++ b/server/private/routers/ssh/signSshKey.ts @@ -24,7 +24,7 @@ import { eq, or, and } from "drizzle-orm"; import { canUserAccessSiteResource } from "@server/auth/canUserAccessSiteResource"; import { signPublicKey, getOrgCAKeys } from "#private/lib/sshCA"; import config from "@server/lib/config"; -import { sendToClient } from "#dynamic/routers/ws"; +import { sendToClient } from "#private/routers/ws"; const paramsSchema = z.strictObject({ orgId: z.string().nonempty() @@ -352,13 +352,13 @@ export async function signSshKey( data: { messageId: message.messageId, orgId: orgId, - agentPort: 8080, + agentPort: 22123, agentHost: resource.destination, - caCert: publicKey, + caCert: caKeys.publicKeyOpenSSH, username: usernameToUse, niceId: resource.niceId, metadata: { - sudo: true, + sudo: true, // we are hardcoding these for now but should make configurable from the role or something homedir: true } } @@ -366,12 +366,19 @@ export async function signSshKey( const expiresIn = Number(validFor); // seconds + let sshHost; + if (resource.alias && resource.alias != "") { + sshHost = resource.alias; + } else { + sshHost = resource.destination; + } + return response(res, { data: { certificate: cert.certificate, messageId: message.messageId, sshUsername: usernameToUse, - sshHost: resource.destination, + sshHost: sshHost, resourceId: resource.siteResourceId, keyId: cert.keyId, validPrincipals: cert.validPrincipals, From b8c3cc751ad18da3ca6be6faf048a3f693506db1 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Tue, 17 Feb 2026 14:37:46 -0800 Subject: [PATCH 65/81] support creating multiple orgs in saas --- messages/en-US.json | 2 + server/db/pg/schema/schema.ts | 4 +- server/db/sqlite/schema/schema.ts | 4 +- server/routers/auth/deleteMyAccount.ts | 44 +- server/routers/auth/signup.ts | 21 - server/routers/external.ts | 5 +- server/routers/org/createOrg.ts | 59 ++- server/routers/org/listUserOrgs.ts | 9 +- .../settings/(private)/billing/layout.tsx | 8 + .../settings/(private)/license/layout.tsx | 22 + src/app/[orgId]/settings/layout.tsx | 6 +- src/app/navigation.tsx | 33 +- src/app/page.tsx | 10 +- src/app/setup/page.tsx | 501 +++++++++--------- src/components/LayoutSidebar.tsx | 4 +- src/components/OrgSelector.tsx | 38 +- 16 files changed, 439 insertions(+), 331 deletions(-) diff --git a/messages/en-US.json b/messages/en-US.json index 3e825711..ecef7605 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -1266,6 +1266,7 @@ "sidebarLogAndAnalytics": "Log & Analytics", "sidebarBluePrints": "Blueprints", "sidebarOrganization": "Organization", + "sidebarBillingAndLicenses": "Billing & Licenses", "sidebarLogsAnalytics": "Analytics", "blueprints": "Blueprints", "blueprintsDescription": "Apply declarative configurations and view previous runs", @@ -1469,6 +1470,7 @@ "failed": "Failed", "createNewOrgDescription": "Create a new organization", "organization": "Organization", + "primary": "Primary", "port": "Port", "securityKeyManage": "Manage Security Keys", "securityKeyDescription": "Add or remove security keys for passwordless authentication", diff --git a/server/db/pg/schema/schema.ts b/server/db/pg/schema/schema.ts index ca46e207..7c252b8b 100644 --- a/server/db/pg/schema/schema.ts +++ b/server/db/pg/schema/schema.ts @@ -55,7 +55,9 @@ export const orgs = pgTable("orgs", { .notNull() .default(0), sshCaPrivateKey: text("sshCaPrivateKey"), // Encrypted SSH CA private key (PEM format) - sshCaPublicKey: text("sshCaPublicKey") // SSH CA public key (OpenSSH format) + sshCaPublicKey: text("sshCaPublicKey"), // SSH CA public key (OpenSSH format) + isBillingOrg: boolean("isBillingOrg"), + billingOrgId: varchar("billingOrgId") }); export const orgDomains = pgTable("orgDomains", { diff --git a/server/db/sqlite/schema/schema.ts b/server/db/sqlite/schema/schema.ts index ce08dea1..04d4338a 100644 --- a/server/db/sqlite/schema/schema.ts +++ b/server/db/sqlite/schema/schema.ts @@ -47,7 +47,9 @@ export const orgs = sqliteTable("orgs", { .notNull() .default(0), sshCaPrivateKey: text("sshCaPrivateKey"), // Encrypted SSH CA private key (PEM format) - sshCaPublicKey: text("sshCaPublicKey") // SSH CA public key (OpenSSH format) + sshCaPublicKey: text("sshCaPublicKey"), // SSH CA public key (OpenSSH format) + isBillingOrg: integer("isBillingOrg", { mode: "boolean" }), + billingOrgId: text("billingOrgId") }); export const userDomains = sqliteTable("userDomains", { diff --git a/server/routers/auth/deleteMyAccount.ts b/server/routers/auth/deleteMyAccount.ts index 2c37cd09..8df11243 100644 --- a/server/routers/auth/deleteMyAccount.ts +++ b/server/routers/auth/deleteMyAccount.ts @@ -15,11 +15,10 @@ import { import { verifyPassword } from "@server/auth/password"; import { verifyTotpCode } from "@server/auth/totp"; import { calculateUserClientsForOrgs } from "@server/lib/calculateUserClientsForOrgs"; -import { - deleteOrgById, - sendTerminationMessages -} from "@server/lib/deleteOrg"; +import { deleteOrgById, sendTerminationMessages } from "@server/lib/deleteOrg"; import { UserType } from "@server/types/UserTypes"; +import { build } from "@server/build"; +import { getOrgTierData } from "#dynamic/lib/billing"; const deleteMyAccountBody = z.strictObject({ password: z.string().optional(), @@ -40,11 +39,6 @@ export type DeleteMyAccountSuccessResponse = { success: true; }; -/** - * Self-service account deletion (saas only). Returns preview when no password; - * requires password and optional 2FA code to perform deletion. Uses shared - * deleteOrgById for each owned org (delete-my-account may delete multiple orgs). - */ export async function deleteMyAccount( req: Request, res: Response, @@ -91,18 +85,35 @@ export async function deleteMyAccount( const ownedOrgsRows = await db .select({ - orgId: userOrgs.orgId + orgId: userOrgs.orgId, + isOwner: userOrgs.isOwner, + isBillingOrg: orgs.isBillingOrg }) .from(userOrgs) + .innerJoin(orgs, eq(userOrgs.orgId, orgs.orgId)) .where( - and( - eq(userOrgs.userId, userId), - eq(userOrgs.isOwner, true) - ) + and(eq(userOrgs.userId, userId), eq(userOrgs.isOwner, true)) ); const orgIds = ownedOrgsRows.map((r) => r.orgId); + if (build === "saas" && orgIds.length > 0) { + const primaryOrgId = ownedOrgsRows.find( + (r) => r.isBillingOrg && r.isOwner + )?.orgId; + if (primaryOrgId) { + const { tier, active } = await getOrgTierData(primaryOrgId); + if (active && tier) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + "You must cancel your subscription before deleting your account" + ) + ); + } + } + } + if (!password) { const orgsWithNames = orgIds.length > 0 @@ -219,10 +230,7 @@ export async function deleteMyAccount( } catch (error) { logger.error(error); return next( - createHttpError( - HttpCode.INTERNAL_SERVER_ERROR, - "An error occurred" - ) + createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred") ); } } diff --git a/server/routers/auth/signup.ts b/server/routers/auth/signup.ts index 2605a026..c1c344d8 100644 --- a/server/routers/auth/signup.ts +++ b/server/routers/auth/signup.ts @@ -21,7 +21,6 @@ import { hashPassword } from "@server/auth/password"; import { checkValidInvite } from "@server/auth/checkValidInvite"; import { passwordSchema } from "@server/auth/passwordSchema"; import { UserType } from "@server/types/UserTypes"; -import { createUserAccountOrg } from "@server/lib/createUserAccountOrg"; import { build } from "@server/build"; import resend, { AudienceIds, moveEmailToAudience } from "#dynamic/lib/resend"; @@ -198,26 +197,6 @@ export async function signup( // orgId: null, // }); - if (build == "saas") { - const { success, error, org } = await createUserAccountOrg( - userId, - email - ); - if (!success) { - if (error) { - return next( - createHttpError(HttpCode.INTERNAL_SERVER_ERROR, error) - ); - } - return next( - createHttpError( - HttpCode.INTERNAL_SERVER_ERROR, - "Failed to create user account and organization" - ) - ); - } - } - const token = generateSessionToken(); const sess = await createSession(token, userId); const isSecure = req.protocol === "https"; diff --git a/server/routers/external.ts b/server/routers/external.ts index a9d075a6..51cd51f9 100644 --- a/server/routers/external.ts +++ b/server/routers/external.ts @@ -65,9 +65,8 @@ authenticated.use(verifySessionUserMiddleware); authenticated.get("/pick-org-defaults", org.pickOrgDefaults); authenticated.get("/org/checkId", org.checkId); -if (build === "oss" || build === "enterprise") { - authenticated.put("/org", getUserOrgs, org.createOrg); -} + +authenticated.put("/org", getUserOrgs, org.createOrg); authenticated.get("/orgs", verifyUserIsServerAdmin, org.listOrgs); authenticated.get("/user/:userId/orgs", verifyIsLoggedInUser, org.listUserOrgs); diff --git a/server/routers/org/createOrg.ts b/server/routers/org/createOrg.ts index b8e2d625..0c135e5b 100644 --- a/server/routers/org/createOrg.ts +++ b/server/routers/org/createOrg.ts @@ -1,7 +1,7 @@ import { Request, Response, NextFunction } from "express"; import { z } from "zod"; import { db } from "@server/db"; -import { eq } from "drizzle-orm"; +import { and, eq } from "drizzle-orm"; import { domains, Org, @@ -24,7 +24,11 @@ import { OpenAPITags, registry } from "@server/openApi"; import { isValidCIDR } from "@server/lib/validators"; import { createCustomer } from "#dynamic/lib/billing"; import { usageService } from "@server/lib/billing/usageService"; -import { FeatureId } from "@server/lib/billing"; +import { + FeatureId, + limitsService, + sandboxLimitSet +} from "@server/lib/billing"; import { build } from "@server/build"; import { calculateUserClientsForOrgs } from "@server/lib/calculateUserClientsForOrgs"; import { doCidrsOverlap } from "@server/lib/ip"; @@ -136,6 +140,40 @@ export async function createOrg( ); } + let isFirstOrg: boolean | null = null; + let billingOrgIdForNewOrg: string | null = null; + if (build === "saas" && req.user) { + const ownedOrgs = await db + .select() + .from(userOrgs) + .where( + and( + eq(userOrgs.userId, req.user.userId), + eq(userOrgs.isOwner, true) + ) + ); + if (ownedOrgs.length === 0) { + isFirstOrg = true; + } else { + isFirstOrg = false; + const [billingOrg] = await db + .select({ orgId: orgs.orgId }) + .from(orgs) + .innerJoin(userOrgs, eq(orgs.orgId, userOrgs.orgId)) + .where( + and( + eq(userOrgs.userId, req.user.userId), + eq(userOrgs.isOwner, true), + eq(orgs.isBillingOrg, true) + ) + ) + .limit(1); + if (billingOrg) { + billingOrgIdForNewOrg = billingOrg.orgId; + } + } + } + let error = ""; let org: Org | null = null; @@ -150,6 +188,16 @@ export async function createOrg( const encryptionKey = config.getRawConfig().server.secret!; const encryptedCaPrivateKey = encrypt(ca.privateKeyPem, encryptionKey); + const saasBillingFields = + build === "saas" && req.user && isFirstOrg !== null + ? isFirstOrg + ? { isBillingOrg: true as const, billingOrgId: null } + : { + isBillingOrg: false as const, + billingOrgId: billingOrgIdForNewOrg + } + : {}; + const newOrg = await trx .insert(orgs) .values({ @@ -159,7 +207,8 @@ export async function createOrg( utilitySubnet, createdAt: new Date().toISOString(), sshCaPrivateKey: encryptedCaPrivateKey, - sshCaPublicKey: ca.publicKeyOpenSSH + sshCaPublicKey: ca.publicKeyOpenSSH, + ...saasBillingFields }) .returning(); @@ -276,8 +325,8 @@ export async function createOrg( return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, error)); } - if (build == "saas") { - // make sure we have the stripe customer + if (build === "saas" && isFirstOrg === true) { + await limitsService.applyLimitSetToOrg(orgId, sandboxLimitSet); const customerId = await createCustomer(orgId, req.user?.email); if (customerId) { await usageService.updateCount( diff --git a/server/routers/org/listUserOrgs.ts b/server/routers/org/listUserOrgs.ts index 103b1023..301d0203 100644 --- a/server/routers/org/listUserOrgs.ts +++ b/server/routers/org/listUserOrgs.ts @@ -40,7 +40,11 @@ const listOrgsSchema = z.object({ // responses: {} // }); -type ResponseOrg = Org & { isOwner?: boolean; isAdmin?: boolean }; +type ResponseOrg = Org & { + isOwner?: boolean; + isAdmin?: boolean; + isPrimaryOrg?: boolean; +}; export type ListUserOrgsResponse = { orgs: ResponseOrg[]; @@ -132,6 +136,9 @@ export async function listUserOrgs( if (val.roles && val.roles.isAdmin) { res.isAdmin = val.roles.isAdmin; } + if (val.userOrgs?.isOwner && val.orgs?.isBillingOrg) { + res.isPrimaryOrg = val.orgs.isBillingOrg; + } return res; }); diff --git a/src/app/[orgId]/settings/(private)/billing/layout.tsx b/src/app/[orgId]/settings/(private)/billing/layout.tsx index c4048bcc..69c3da48 100644 --- a/src/app/[orgId]/settings/(private)/billing/layout.tsx +++ b/src/app/[orgId]/settings/(private)/billing/layout.tsx @@ -6,6 +6,7 @@ import { redirect } from "next/navigation"; import { getTranslations } from "next-intl/server"; import { getCachedOrgUser } from "@app/lib/api/getCachedOrgUser"; import { getCachedOrg } from "@app/lib/api/getCachedOrg"; +import { build } from "@server/build"; type BillingSettingsProps = { children: React.ReactNode; @@ -17,6 +18,9 @@ export default async function BillingSettingsPage({ params }: BillingSettingsProps) { const { orgId } = await params; + if (build !== "saas") { + redirect(`/${orgId}/settings`); + } const user = await verifySession(); @@ -40,6 +44,10 @@ export default async function BillingSettingsPage({ redirect(`/${orgId}`); } + if (!(org?.org?.isBillingOrg && orgUser?.isOwner)) { + redirect(`/${orgId}`); + } + const t = await getTranslations(); return ( diff --git a/src/app/[orgId]/settings/(private)/license/layout.tsx b/src/app/[orgId]/settings/(private)/license/layout.tsx index 9083bb81..453b3372 100644 --- a/src/app/[orgId]/settings/(private)/license/layout.tsx +++ b/src/app/[orgId]/settings/(private)/license/layout.tsx @@ -4,6 +4,8 @@ import { redirect } from "next/navigation"; import { cache } from "react"; import { getTranslations } from "next-intl/server"; import { build } from "@server/build"; +import { getCachedOrgUser } from "@app/lib/api/getCachedOrgUser"; +import { getCachedOrg } from "@app/lib/api/getCachedOrg"; type LicensesSettingsProps = { children: React.ReactNode; @@ -27,6 +29,26 @@ export default async function LicensesSetingsLayoutProps({ redirect(`/`); } + let orgUser = null; + try { + const res = await getCachedOrgUser(orgId, user.userId); + orgUser = res.data.data; + } catch { + redirect(`/${orgId}`); + } + + let org = null; + try { + const res = await getCachedOrg(orgId); + org = res.data.data; + } catch { + redirect(`/${orgId}`); + } + + if (!org?.org?.isBillingOrg || !orgUser?.isOwner) { + redirect(`/${orgId}`); + } + const t = await getTranslations(); return ( diff --git a/src/app/[orgId]/settings/layout.tsx b/src/app/[orgId]/settings/layout.tsx index 34ed3ac2..8ee7b1dc 100644 --- a/src/app/[orgId]/settings/layout.tsx +++ b/src/app/[orgId]/settings/layout.tsx @@ -77,12 +77,16 @@ export default async function SettingsLayout(props: SettingsLayoutProps) { } } catch (e) {} + const primaryOrg = orgs.find((o) => o.orgId === params.orgId)?.isPrimaryOrg; + return ( {children} diff --git a/src/app/navigation.tsx b/src/app/navigation.tsx index 7df4364a..be3ad7d3 100644 --- a/src/app/navigation.tsx +++ b/src/app/navigation.tsx @@ -31,6 +31,10 @@ export type SidebarNavSection = { items: SidebarNavItem[]; }; +export type OrgNavSectionsOptions = { + isPrimaryOrg?: boolean; +}; + // Merged from 'user-management-and-resources' branch export const orgLangingNavItems: SidebarNavItem[] = [ { @@ -40,7 +44,10 @@ export const orgLangingNavItems: SidebarNavItem[] = [ } ]; -export const orgNavSections = (env?: Env): SidebarNavSection[] => [ +export const orgNavSections = ( + env?: Env, + options?: OrgNavSectionsOptions +): SidebarNavSection[] => [ { heading: "sidebarGeneral", items: [ @@ -214,28 +221,28 @@ export const orgNavSections = (env?: Env): SidebarNavSection[] => [ title: "sidebarSettings", href: "/{orgId}/settings/general", icon: - }, - - ...(build == "saas" - ? [ + } + ] + }, + ...(build == "saas" && options?.isPrimaryOrg + ? [ + { + heading: "sidebarBillingAndLicenses", + items: [ { title: "sidebarBilling", href: "/{orgId}/settings/billing", icon: - } - ] - : []), - ...(build == "saas" - ? [ + }, { title: "sidebarEnterpriseLicenses", href: "/{orgId}/settings/license", icon: } ] - : []) - ] - } + } + ] + : []) ]; export const adminNavSections = (env?: Env): SidebarNavSection[] => [ diff --git a/src/app/page.tsx b/src/app/page.tsx index df1a81df..f6f30276 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -73,7 +73,7 @@ export default async function Page(props: { if (!orgs.length) { if (!env.flags.disableUserCreateOrg || user.serverAdmin) { - redirect("/setup"); + redirect("/setup?firstOrg"); } } @@ -86,6 +86,14 @@ export default async function Page(props: { targetOrgId = lastOrgCookie; } else { let ownedOrg = orgs.find((org) => org.isOwner); + let primaryOrg = orgs.find((org) => org.isPrimaryOrg); + if (!ownedOrg) { + if (primaryOrg) { + ownedOrg = primaryOrg; + } else { + ownedOrg = orgs[0]; + } + } if (!ownedOrg) { ownedOrg = orgs[0]; } diff --git a/src/app/setup/page.tsx b/src/app/setup/page.tsx index c8b2af19..dc505b67 100644 --- a/src/app/setup/page.tsx +++ b/src/app/setup/page.tsx @@ -4,19 +4,14 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { toast } from "@app/hooks/useToast"; import { useCallback, useEffect, useState } from "react"; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle -} from "@app/components/ui/card"; import { formatAxiosError } from "@app/lib/api"; import { createApiClient } from "@app/lib/api"; import { useEnvContext } from "@app/hooks/useEnvContext"; +import { useUserContext } from "@app/hooks/useUserContext"; +import { build } from "@server/build"; import { Separator } from "@/components/ui/separator"; import { z } from "zod"; -import { useRouter } from "next/navigation"; +import { useRouter, useSearchParams } from "next/navigation"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { @@ -35,7 +30,7 @@ import { CollapsibleContent, CollapsibleTrigger } from "@app/components/ui/collapsible"; -import { ChevronsUpDown } from "lucide-react"; +import { ArrowRight, ChevronsUpDown } from "lucide-react"; import { cn } from "@app/lib/cn"; type Step = "org" | "site" | "resources"; @@ -45,6 +40,7 @@ export default function StepperForm() { const [orgIdTaken, setOrgIdTaken] = useState(false); const t = useTranslations(); const { env } = useEnvContext(); + const { user } = useUserContext(); const [loading, setLoading] = useState(false); const [isChecked, setIsChecked] = useState(false); @@ -71,12 +67,27 @@ export default function StepperForm() { const api = createApiClient(useEnvContext()); const router = useRouter(); + const searchParams = useSearchParams(); + const isFirstOrg = searchParams.get("firstOrg") != null; // Fetch default subnet on component mount useEffect(() => { fetchDefaultSubnet(); }, []); + // Prefill org name and id when build is saas and firstOrg query param is set + useEffect(() => { + if (build !== "saas" || !user || !isFirstOrg) return; + + const orgName = user.email + ? `${user.email}'s Organization` + : "My Organization"; + const orgId = `org_${user.userId}`; + orgForm.setValue("orgName", orgName); + orgForm.setValue("orgId", orgId); + debouncedCheckOrgIdAvailability(orgId); + }, []); + const fetchDefaultSubnet = async () => { try { const res = await api.get(`/pick-org-defaults`); @@ -161,263 +172,239 @@ export default function StepperForm() { } return ( - <> - - - {t("setupNewOrg")} - {t("setupCreate")} - - -
    -
    -
    -
    - 1 -
    - - {t("setupCreateOrg")} - -
    -
    -
    - 2 -
    - - {t("siteCreate")} - -
    -
    -
    - 3 -
    - - {t("setupCreateResources")} - -
    -
    +
    +
    +

    + {t("setupNewOrg")} +

    +

    + {t("setupCreate")} +

    +
    +
    +
    +
    + 1 +
    + + {t("setupCreateOrg")} + +
    +
    +
    + 2 +
    + + {t("siteCreate")} + +
    +
    +
    + 3 +
    + + {t("setupCreateResources")} + +
    +
    - + - {currentStep === "org" && ( -
    - - ( - - - {t("setupOrgName")} - - - { - // Prevent "/" in orgName input - const sanitizedValue = - e.target.value.replace( - /\//g, - "-" - ); - const orgId = - generateId( - sanitizedValue - ); - orgForm.setValue( - "orgId", - orgId - ); - orgForm.setValue( - "orgName", - sanitizedValue - ); - debouncedCheckOrgIdAvailability( - orgId - ); - }} - value={field.value.replace( - /\//g, - "-" - )} - /> - - - - {t("orgDisplayName")} - - - )} - /> - ( - - - {t("orgId")} - - - - - - - {t( - "setupIdentifierMessage" - )} - - - )} - /> + {currentStep === "org" && ( + + + ( + + {t("setupOrgName")} + + { + // Prevent "/" in orgName input + const sanitizedValue = + e.target.value.replace( + /\//g, + "-" + ); + const orgId = + generateId(sanitizedValue); + orgForm.setValue( + "orgId", + orgId + ); + orgForm.setValue( + "orgName", + sanitizedValue + ); + debouncedCheckOrgIdAvailability( + orgId + ); + }} + value={field.value.replace( + /\//g, + "-" + )} + /> + + + + {t("orgDisplayName")} + + + )} + /> + ( + + {t("orgId")} + + + + + + {t("setupIdentifierMessage")} + + + )} + /> - +
    + + - +

    + {t("advancedSettings")} +

    +
    + + + {t("toggle")} +
    - - ( - - - {t( - "setupSubnetAdvanced" - )} - - - - - - - {t( - "setupSubnetDescription" - )} - - + + +
    + + ( + + + {t("setupSubnetAdvanced")} + + + + + + + {t("setupSubnetDescription")} + + + )} + /> + + ( + + + {t("setupUtilitySubnet")} + + + + + + + {t( + "setupUtilitySubnetDescription" )} - /> + + + )} + /> + +
    - ( - - - {t( - "setupUtilitySubnet" - )} - - - - - - - {t( - "setupUtilitySubnetDescription" - )} - - - )} - /> - - + {orgIdTaken && !orgCreated ? ( + + + {t("setupErrorIdentifier")} + + + ) : null} - {orgIdTaken && !orgCreated ? ( - - - {t("setupErrorIdentifier")} - - - ) : null} + {/* Error Alert removed, errors now shown as toast */} - {/* Error Alert removed, errors now shown as toast */} - -
    - -
    - - - )} -
    - - - +
    + +
    + + + )} +
    ); } diff --git a/src/components/LayoutSidebar.tsx b/src/components/LayoutSidebar.tsx index 15951402..3095b1fd 100644 --- a/src/components/LayoutSidebar.tsx +++ b/src/components/LayoutSidebar.tsx @@ -189,10 +189,12 @@ export function LayoutSidebar({
    - {canShowProductUpdates && ( + {canShowProductUpdates ? (
    + ) : ( +
    )} {build === "enterprise" && ( diff --git a/src/components/OrgSelector.tsx b/src/components/OrgSelector.tsx index e139e43a..45fed43c 100644 --- a/src/components/OrgSelector.tsx +++ b/src/components/OrgSelector.tsx @@ -20,12 +20,13 @@ import { TooltipProvider, TooltipTrigger } from "@app/components/ui/tooltip"; +import { Badge } from "@app/components/ui/badge"; import { useEnvContext } from "@app/hooks/useEnvContext"; import { cn } from "@app/lib/cn"; import { ListUserOrgsResponse } from "@server/routers/org"; import { Check, ChevronsUpDown, Plus, Building2, Users } from "lucide-react"; import { useRouter } from "next/navigation"; -import { useState } from "react"; +import { useMemo, useState } from "react"; import { useUserContext } from "@app/hooks/useUserContext"; import { useTranslations } from "next-intl"; @@ -48,6 +49,17 @@ export function OrgSelector({ const selectedOrg = orgs?.find((org) => org.orgId === orgId); + const sortedOrgs = useMemo(() => { + if (!orgs?.length) return orgs ?? []; + return [...orgs].sort((a, b) => { + const aPrimary = Boolean(a.isPrimaryOrg); + const bPrimary = Boolean(b.isPrimaryOrg); + if (aPrimary && !bPrimary) return -1; + if (!aPrimary && bPrimary) return 1; + return 0; + }); + }, [orgs]); + const orgSelectorContent = ( @@ -124,7 +136,7 @@ export function OrgSelector({ )} - {orgs?.map((org) => ( + {sortedOrgs.map((org) => ( { @@ -136,12 +148,22 @@ export function OrgSelector({
    -
    - - {org.name} - - - {t("organization")} +
    +
    + + {org.name} + + {org.isPrimaryOrg && ( + + {t("primary")} + + )} +
    + + {org.orgId}
    Date: Tue, 17 Feb 2026 14:48:13 -0800 Subject: [PATCH 66/81] Dont create ca certs quite yet --- server/lib/createUserAccountOrg.ts | 10 +++++----- server/routers/org/createOrg.ts | 12 ++++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/server/lib/createUserAccountOrg.ts b/server/lib/createUserAccountOrg.ts index a40407d1..207594a5 100644 --- a/server/lib/createUserAccountOrg.ts +++ b/server/lib/createUserAccountOrg.ts @@ -82,9 +82,9 @@ export async function createUserAccountOrg( const utilitySubnet = config.getRawConfig().orgs.utility_subnet_group; // Generate SSH CA keys for the org - const ca = generateCA(`${orgId}-ca`); - const encryptionKey = config.getRawConfig().server.secret!; - const encryptedCaPrivateKey = encrypt(ca.privateKeyPem, encryptionKey); + // const ca = generateCA(`${orgId}-ca`); + // const encryptionKey = config.getRawConfig().server.secret!; + // const encryptedCaPrivateKey = encrypt(ca.privateKeyPem, encryptionKey); const newOrg = await trx .insert(orgs) @@ -95,8 +95,8 @@ export async function createUserAccountOrg( subnet: "100.90.128.0/24", // TODO: this should not be hardcoded - or can it be the same in all orgs? utilitySubnet: utilitySubnet, createdAt: new Date().toISOString(), - sshCaPrivateKey: encryptedCaPrivateKey, - sshCaPublicKey: ca.publicKeyOpenSSH + // sshCaPrivateKey: encryptedCaPrivateKey, + // sshCaPublicKey: ca.publicKeyOpenSSH }) .returning(); diff --git a/server/routers/org/createOrg.ts b/server/routers/org/createOrg.ts index b8e2d625..22e9314e 100644 --- a/server/routers/org/createOrg.ts +++ b/server/routers/org/createOrg.ts @@ -145,10 +145,10 @@ export async function createOrg( .from(domains) .where(eq(domains.configManaged, true)); - // Generate SSH CA keys for the org - const ca = generateCA(`${orgId}-ca`); - const encryptionKey = config.getRawConfig().server.secret!; - const encryptedCaPrivateKey = encrypt(ca.privateKeyPem, encryptionKey); + // // Generate SSH CA keys for the org + // const ca = generateCA(`${orgId}-ca`); + // const encryptionKey = config.getRawConfig().server.secret!; + // const encryptedCaPrivateKey = encrypt(ca.privateKeyPem, encryptionKey); const newOrg = await trx .insert(orgs) @@ -158,8 +158,8 @@ export async function createOrg( subnet, utilitySubnet, createdAt: new Date().toISOString(), - sshCaPrivateKey: encryptedCaPrivateKey, - sshCaPublicKey: ca.publicKeyOpenSSH + // sshCaPrivateKey: encryptedCaPrivateKey, + // sshCaPublicKey: ca.publicKeyOpenSSH }) .returning(); From b71f582329f4835e5a5589514c3488d722f95bc7 Mon Sep 17 00:00:00 2001 From: Owen Date: Tue, 17 Feb 2026 15:09:42 -0800 Subject: [PATCH 67/81] Use the billing org id when updating and checking usage --- server/lib/billing/usageService.ts | 346 ++++++++++++++++------------- 1 file changed, 195 insertions(+), 151 deletions(-) diff --git a/server/lib/billing/usageService.ts b/server/lib/billing/usageService.ts index c4ae2925..03fa42a8 100644 --- a/server/lib/billing/usageService.ts +++ b/server/lib/billing/usageService.ts @@ -10,7 +10,8 @@ import { limits, Usage, Limit, - Transaction + Transaction, + orgs } from "@server/db"; import { FeatureId, getFeatureMeterId } from "./features"; import logger from "@server/logger"; @@ -37,10 +38,10 @@ export function noop() { } export class UsageService { - private bucketName: string | undefined; - private events: StripeEvent[] = []; - private lastUploadTime: number = Date.now(); - private isUploading: boolean = false; + // private bucketName: string | undefined; + // private events: StripeEvent[] = []; + // private lastUploadTime: number = Date.now(); + // private isUploading: boolean = false; constructor() { if (noop()) { @@ -91,6 +92,8 @@ export class UsageService { return null; } + let orgIdToUse = await this.getBillingOrg(orgId, transaction); + // Truncate value to 11 decimal places value = this.truncateValue(value); @@ -100,20 +103,20 @@ export class UsageService { while (attempt <= maxRetries) { try { - // Get subscription data for this org (with caching) - const customerId = await this.getCustomerId(orgId, featureId); + // // Get subscription data for this org (with caching) + // const customerId = await this.getCustomerId(orgIdToUse, featureId); - if (!customerId) { - logger.warn( - `No subscription data found for org ${orgId} and feature ${featureId}` - ); - return null; - } + // if (!customerId) { + // logger.warn( + // `No subscription data found for org ${orgIdToUse} and feature ${featureId}` + // ); + // return null; + // } let usage; if (transaction) { usage = await this.internalAddUsage( - orgId, + orgIdToUse, featureId, value, transaction @@ -121,7 +124,7 @@ export class UsageService { } else { await db.transaction(async (trx) => { usage = await this.internalAddUsage( - orgId, + orgIdToUse, featureId, value, trx @@ -150,7 +153,7 @@ export class UsageService { const delay = baseDelay + jitter; logger.warn( - `Deadlock detected for ${orgId}/${featureId}, retrying attempt ${attempt}/${maxRetries} after ${delay.toFixed(0)}ms` + `Deadlock detected for ${orgIdToUse}/${featureId}, retrying attempt ${attempt}/${maxRetries} after ${delay.toFixed(0)}ms` ); await new Promise((resolve) => setTimeout(resolve, delay)); @@ -158,7 +161,7 @@ export class UsageService { } logger.error( - `Failed to add usage for ${orgId}/${featureId} after ${attempt} attempts:`, + `Failed to add usage for ${orgIdToUse}/${featureId} after ${attempt} attempts:`, error ); break; @@ -169,7 +172,7 @@ export class UsageService { } private async internalAddUsage( - orgId: string, + orgId: string, // here the orgId is the billing org already resolved by getBillingOrg in updateCount featureId: FeatureId, value: number, trx: Transaction @@ -221,17 +224,20 @@ export class UsageService { if (noop()) { return; } + + let orgIdToUse = await this.getBillingOrg(orgId); + try { - if (!customerId) { - customerId = - (await this.getCustomerId(orgId, featureId)) || undefined; - if (!customerId) { - logger.warn( - `No subscription data found for org ${orgId} and feature ${featureId}` - ); - return; - } - } + // if (!customerId) { + // customerId = + // (await this.getCustomerId(orgIdToUse, featureId)) || undefined; + // if (!customerId) { + // logger.warn( + // `No subscription data found for org ${orgIdToUse} and feature ${featureId}` + // ); + // return; + // } + // } // Truncate value to 11 decimal places if provided if (value !== undefined && value !== null) { @@ -242,7 +248,7 @@ export class UsageService { await db.transaction(async (trx) => { // Get existing meter record - const usageId = `${orgId}-${featureId}`; + const usageId = `${orgIdToUse}-${featureId}`; // Get current usage record [currentUsage] = await trx .select() @@ -264,7 +270,7 @@ export class UsageService { await trx.insert(usage).values({ usageId, featureId, - orgId, + orgId: orgIdToUse, meterId, instantaneousValue: value || 0, latestValue: value || 0, @@ -278,7 +284,7 @@ export class UsageService { // } } catch (error) { logger.error( - `Failed to update count usage for ${orgId}/${featureId}:`, + `Failed to update count usage for ${orgIdToUse}/${featureId}:`, error ); } @@ -288,7 +294,9 @@ export class UsageService { orgId: string, featureId: FeatureId ): Promise { - const cacheKey = `customer_${orgId}_${featureId}`; + let orgIdToUse = await this.getBillingOrg(orgId); + + const cacheKey = `customer_${orgIdToUse}_${featureId}`; const cached = cache.get(cacheKey); if (cached) { @@ -302,7 +310,7 @@ export class UsageService { customerId: customers.customerId }) .from(customers) - .where(eq(customers.orgId, orgId)) + .where(eq(customers.orgId, orgIdToUse)) .limit(1); if (!customer) { @@ -317,112 +325,13 @@ export class UsageService { return customerId; } catch (error) { logger.error( - `Failed to get subscription data for ${orgId}/${featureId}:`, + `Failed to get subscription data for ${orgIdToUse}/${featureId}:`, error ); return null; } } - private async logStripeEvent( - featureId: FeatureId, - value: number, - customerId: string - ): Promise { - // Truncate value to 11 decimal places before sending to Stripe - const truncatedValue = this.truncateValue(value); - - const event: StripeEvent = { - identifier: uuidv4(), - timestamp: Math.floor(new Date().getTime() / 1000), - event_name: featureId, - payload: { - value: truncatedValue, - stripe_customer_id: customerId - } - }; - - this.addEventToMemory(event); - await this.checkAndUploadEvents(); - } - - private addEventToMemory(event: StripeEvent): void { - if (!this.bucketName) { - logger.warn( - "S3 bucket name is not configured, skipping event storage." - ); - return; - } - this.events.push(event); - } - - private async checkAndUploadEvents(): Promise { - const now = Date.now(); - const timeSinceLastUpload = now - this.lastUploadTime; - - // Check if at least 1 minute has passed since last upload - if (timeSinceLastUpload >= 60000 && this.events.length > 0) { - await this.uploadEventsToS3(); - } - } - - private async uploadEventsToS3(): Promise { - if (!this.bucketName) { - logger.warn( - "S3 bucket name is not configured, skipping S3 upload." - ); - return; - } - - if (this.events.length === 0) { - return; - } - - // Check if already uploading - if (this.isUploading) { - logger.debug("Already uploading events, skipping"); - return; - } - - this.isUploading = true; - - try { - // Take a snapshot of current events and clear the array - const eventsToUpload = [...this.events]; - this.events = []; - this.lastUploadTime = Date.now(); - - const fileName = this.generateEventFileName(); - const fileContent = JSON.stringify(eventsToUpload, null, 2); - - // Upload to S3 - const uploadCommand = new PutObjectCommand({ - Bucket: this.bucketName, - Key: fileName, - Body: fileContent, - ContentType: "application/json" - }); - - await s3Client.send(uploadCommand); - - logger.info( - `Uploaded ${fileName} to S3 with ${eventsToUpload.length} events` - ); - } catch (error) { - logger.error("Failed to upload events to S3:", error); - // Note: Events are lost if upload fails. In a production system, - // you might want to add the events back to the array or implement retry logic - } finally { - this.isUploading = false; - } - } - - private generateEventFileName(): string { - const timestamp = new Date().toISOString().replace(/[:.]/g, "-"); - const uuid = uuidv4().substring(0, 8); - return `events-${timestamp}-${uuid}.json`; - } - public async getUsage( orgId: string, featureId: FeatureId, @@ -432,7 +341,9 @@ export class UsageService { return null; } - const usageId = `${orgId}-${featureId}`; + let orgIdToUse = await this.getBillingOrg(orgId, trx); + + const usageId = `${orgIdToUse}-${featureId}`; try { const [result] = await trx @@ -444,7 +355,7 @@ export class UsageService { if (!result) { // Lets create one if it doesn't exist using upsert to handle race conditions logger.info( - `Creating new usage record for ${orgId}/${featureId}` + `Creating new usage record for ${orgIdToUse}/${featureId}` ); const meterId = getFeatureMeterId(featureId); @@ -454,7 +365,7 @@ export class UsageService { .values({ usageId, featureId, - orgId, + orgId: orgIdToUse, meterId, latestValue: 0, updatedAt: Math.floor(Date.now() / 1000) @@ -476,7 +387,7 @@ export class UsageService { } catch (insertError) { // Fallback: try to fetch existing record in case of any insert issues logger.warn( - `Insert failed for ${orgId}/${featureId}, attempting to fetch existing record:`, + `Insert failed for ${orgIdToUse}/${featureId}, attempting to fetch existing record:`, insertError ); const [existingUsage] = await trx @@ -491,19 +402,41 @@ export class UsageService { return result; } catch (error) { logger.error( - `Failed to get usage for ${orgId}/${featureId}:`, + `Failed to get usage for ${orgIdToUse}/${featureId}:`, error ); throw error; } } - public async forceUpload(): Promise { - if (this.events.length > 0) { - // Force upload regardless of time - this.lastUploadTime = 0; // Reset to force upload - await this.uploadEventsToS3(); + public async getBillingOrg( + orgId: string, + trx: Transaction | typeof db = db + ): Promise { + let orgIdToUse = orgId; + + // get the org + const [org] = await trx + .select() + .from(orgs) + .where(eq(orgs.orgId, orgId)) + .limit(1); + + if (!org) { + throw new Error(`Organization with ID ${orgId} not found`); } + + if (!org.isBillingOrg) { + if (org.billingOrgId) { + orgIdToUse = org.billingOrgId; + } else { + throw new Error( + `Organization ${orgId} is not a billing org and does not have a billingOrgId set` + ); + } + } + + return orgIdToUse; } public async checkLimitSet( @@ -515,6 +448,9 @@ export class UsageService { if (noop()) { return false; } + + let orgIdToUse = await this.getBillingOrg(orgId, trx); + // This method should check the current usage against the limits set for the organization // and kick out all of the sites on the org let hasExceededLimits = false; @@ -528,7 +464,7 @@ export class UsageService { .from(limits) .where( and( - eq(limits.orgId, orgId), + eq(limits.orgId, orgIdToUse), eq(limits.featureId, featureId) ) ); @@ -537,11 +473,11 @@ export class UsageService { orgLimits = await trx .select() .from(limits) - .where(eq(limits.orgId, orgId)); + .where(eq(limits.orgId, orgIdToUse)); } if (orgLimits.length === 0) { - logger.debug(`No limits set for org ${orgId}`); + logger.debug(`No limits set for org ${orgIdToUse}`); return false; } @@ -552,7 +488,7 @@ export class UsageService { currentUsage = usage; } else { currentUsage = await this.getUsage( - orgId, + orgIdToUse, limit.featureId as FeatureId, trx ); @@ -563,10 +499,10 @@ export class UsageService { currentUsage?.latestValue || 0; logger.debug( - `Current usage for org ${orgId} on feature ${limit.featureId}: ${usageValue}` + `Current usage for org ${orgIdToUse} on feature ${limit.featureId}: ${usageValue}` ); logger.debug( - `Limit for org ${orgId} on feature ${limit.featureId}: ${limit.value}` + `Limit for org ${orgIdToUse} on feature ${limit.featureId}: ${limit.value}` ); if ( currentUsage && @@ -574,7 +510,7 @@ export class UsageService { usageValue > limit.value ) { logger.debug( - `Org ${orgId} has exceeded limit for ${limit.featureId}: ` + + `Org ${orgIdToUse} has exceeded limit for ${limit.featureId}: ` + `${usageValue} > ${limit.value}` ); hasExceededLimits = true; @@ -582,11 +518,119 @@ export class UsageService { } } } catch (error) { - logger.error(`Error checking limits for org ${orgId}:`, error); + logger.error(`Error checking limits for org ${orgIdToUse}:`, error); } return hasExceededLimits; } + + // private async logStripeEvent( + // featureId: FeatureId, + // value: number, + // customerId: string + // ): Promise { + // // Truncate value to 11 decimal places before sending to Stripe + // const truncatedValue = this.truncateValue(value); + + // const event: StripeEvent = { + // identifier: uuidv4(), + // timestamp: Math.floor(new Date().getTime() / 1000), + // event_name: featureId, + // payload: { + // value: truncatedValue, + // stripe_customer_id: customerId + // } + // }; + + // this.addEventToMemory(event); + // await this.checkAndUploadEvents(); + // } + + // private addEventToMemory(event: StripeEvent): void { + // if (!this.bucketName) { + // logger.warn( + // "S3 bucket name is not configured, skipping event storage." + // ); + // return; + // } + // this.events.push(event); + // } + + // private async checkAndUploadEvents(): Promise { + // const now = Date.now(); + // const timeSinceLastUpload = now - this.lastUploadTime; + + // // Check if at least 1 minute has passed since last upload + // if (timeSinceLastUpload >= 60000 && this.events.length > 0) { + // await this.uploadEventsToS3(); + // } + // } + + // private async uploadEventsToS3(): Promise { + // if (!this.bucketName) { + // logger.warn( + // "S3 bucket name is not configured, skipping S3 upload." + // ); + // return; + // } + + // if (this.events.length === 0) { + // return; + // } + + // // Check if already uploading + // if (this.isUploading) { + // logger.debug("Already uploading events, skipping"); + // return; + // } + + // this.isUploading = true; + + // try { + // // Take a snapshot of current events and clear the array + // const eventsToUpload = [...this.events]; + // this.events = []; + // this.lastUploadTime = Date.now(); + + // const fileName = this.generateEventFileName(); + // const fileContent = JSON.stringify(eventsToUpload, null, 2); + + // // Upload to S3 + // const uploadCommand = new PutObjectCommand({ + // Bucket: this.bucketName, + // Key: fileName, + // Body: fileContent, + // ContentType: "application/json" + // }); + + // await s3Client.send(uploadCommand); + + // logger.info( + // `Uploaded ${fileName} to S3 with ${eventsToUpload.length} events` + // ); + // } catch (error) { + // logger.error("Failed to upload events to S3:", error); + // // Note: Events are lost if upload fails. In a production system, + // // you might want to add the events back to the array or implement retry logic + // } finally { + // this.isUploading = false; + // } + // } + + // private generateEventFileName(): string { + // const timestamp = new Date().toISOString().replace(/[:.]/g, "-"); + // const uuid = uuidv4().substring(0, 8); + // return `events-${timestamp}-${uuid}.json`; + // } + + // public async forceUpload(): Promise { + // if (this.events.length > 0) { + // // Force upload regardless of time + // this.lastUploadTime = 0; // Reset to force upload + // await this.uploadEventsToS3(); + // } + // } + } export const usageService = new UsageService(); From 79cf7c84dc2e4deb558948b40a20e6de8328ab3f Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Tue, 17 Feb 2026 16:45:15 -0800 Subject: [PATCH 68/81] support delete org and preserve path on switch --- server/routers/external.ts | 18 +++++----- server/routers/org/deleteOrg.ts | 44 +++++++++++++++++++++++ src/app/[orgId]/settings/general/page.tsx | 8 ++--- src/components/OrgSelector.tsx | 9 +++-- 4 files changed, 61 insertions(+), 18 deletions(-) diff --git a/server/routers/external.ts b/server/routers/external.ts index 51cd51f9..45ab58bb 100644 --- a/server/routers/external.ts +++ b/server/routers/external.ts @@ -86,16 +86,14 @@ authenticated.post( org.updateOrg ); -if (build !== "saas") { - authenticated.delete( - "/org/:orgId", - verifyOrgAccess, - verifyUserIsOrgOwner, - verifyUserHasAction(ActionsEnum.deleteOrg), - logActionAudit(ActionsEnum.deleteOrg), - org.deleteOrg - ); -} +authenticated.delete( + "/org/:orgId", + verifyOrgAccess, + verifyUserIsOrgOwner, + verifyUserHasAction(ActionsEnum.deleteOrg), + logActionAudit(ActionsEnum.deleteOrg), + org.deleteOrg +); authenticated.put( "/org/:orgId/site", diff --git a/server/routers/org/deleteOrg.ts b/server/routers/org/deleteOrg.ts index 0e5b87a2..7de02162 100644 --- a/server/routers/org/deleteOrg.ts +++ b/server/routers/org/deleteOrg.ts @@ -7,6 +7,8 @@ import logger from "@server/logger"; import { fromError } from "zod-validation-error"; import { OpenAPITags, registry } from "@server/openApi"; import { deleteOrgById, sendTerminationMessages } from "@server/lib/deleteOrg"; +import { db, userOrgs, orgs } from "@server/db"; +import { eq, and } from "drizzle-orm"; const deleteOrgSchema = z.strictObject({ orgId: z.string() @@ -41,6 +43,48 @@ export async function deleteOrg( ); } const { orgId } = parsedParams.data; + + const [data] = await db + .select() + .from(userOrgs) + .innerJoin(orgs, eq(userOrgs.orgId, orgs.orgId)) + .where( + and( + eq(userOrgs.orgId, orgId), + eq(userOrgs.userId, req.user!.userId) + ) + ); + + const org = data?.orgs; + const userOrg = data?.userOrgs; + + if (!org || !userOrg) { + return next( + createHttpError( + HttpCode.NOT_FOUND, + `Organization with ID ${orgId} not found` + ) + ); + } + + if (!userOrg.isOwner) { + return next( + createHttpError( + HttpCode.FORBIDDEN, + "Only organization owners can delete the organization" + ) + ); + } + + if (org.isBillingOrg) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + "Cannot delete a primary organization" + ) + ); + } + const result = await deleteOrgById(orgId); sendTerminationMessages(result); return response(res, { diff --git a/src/app/[orgId]/settings/general/page.tsx b/src/app/[orgId]/settings/general/page.tsx index 0b3ae3d5..0a2ed39b 100644 --- a/src/app/[orgId]/settings/general/page.tsx +++ b/src/app/[orgId]/settings/general/page.tsx @@ -3,11 +3,7 @@ import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog"; import { Button } from "@app/components/ui/button"; import { useOrgContext } from "@app/hooks/useOrgContext"; import { toast } from "@app/hooks/useToast"; -import { - useState, - useTransition, - useActionState -} from "react"; +import { useState, useTransition, useActionState } from "react"; import { Form, FormControl, @@ -54,7 +50,7 @@ export default function GeneralPage() { return ( - {build !== "saas" && } + {!org.org.isBillingOrg && } ); } diff --git a/src/components/OrgSelector.tsx b/src/components/OrgSelector.tsx index 45fed43c..f5351362 100644 --- a/src/components/OrgSelector.tsx +++ b/src/components/OrgSelector.tsx @@ -25,7 +25,7 @@ import { useEnvContext } from "@app/hooks/useEnvContext"; import { cn } from "@app/lib/cn"; import { ListUserOrgsResponse } from "@server/routers/org"; import { Check, ChevronsUpDown, Plus, Building2, Users } from "lucide-react"; -import { useRouter } from "next/navigation"; +import { usePathname, useRouter } from "next/navigation"; import { useMemo, useState } from "react"; import { useUserContext } from "@app/hooks/useUserContext"; import { useTranslations } from "next-intl"; @@ -44,6 +44,7 @@ export function OrgSelector({ const { user } = useUserContext(); const [open, setOpen] = useState(false); const router = useRouter(); + const pathname = usePathname(); const { env } = useEnvContext(); const t = useTranslations(); @@ -141,7 +142,11 @@ export function OrgSelector({ key={org.orgId} onSelect={() => { setOpen(false); - router.push(`/${org.orgId}/settings`); + const newPath = pathname.replace( + /^\/[^/]+/, + `/${org.orgId}` + ); + router.push(newPath); }} className="mx-2 rounded-md" > From 4d6240c987332b261b4fe53b5839d68ad0205a1b Mon Sep 17 00:00:00 2001 From: Owen Date: Tue, 17 Feb 2026 17:09:48 -0800 Subject: [PATCH 69/81] Handle new usage tracking with multi org --- server/lib/billing/features.ts | 3 + server/lib/billing/limitSet.ts | 17 +- server/lib/billing/usageService.ts | 183 +--------------- server/lib/createUserAccountOrg.ts | 206 ------------------ server/lib/deleteOrg.ts | 27 +-- server/private/routers/billing/getOrgUsage.ts | 17 +- .../remoteExitNode/createRemoteExitNode.ts | 71 ++++-- .../remoteExitNode/deleteRemoteExitNode.ts | 70 +++--- server/routers/domain/createOrgDomain.ts | 14 +- server/routers/domain/deleteOrgDomain.ts | 15 +- server/routers/org/createOrg.ts | 80 +++++-- server/routers/site/createSite.ts | 16 +- server/routers/site/deleteSite.ts | 15 +- server/routers/user/acceptInvite.ts | 68 ++++-- server/routers/user/createOrgUser.ts | 64 ++++-- server/routers/user/removeUserOrg.ts | 87 ++++++-- .../settings/(private)/billing/page.tsx | 63 +++++- 17 files changed, 432 insertions(+), 584 deletions(-) delete mode 100644 server/lib/createUserAccountOrg.ts diff --git a/server/lib/billing/features.ts b/server/lib/billing/features.ts index 3fec53b4..6063b470 100644 --- a/server/lib/billing/features.ts +++ b/server/lib/billing/features.ts @@ -4,6 +4,7 @@ export enum FeatureId { EGRESS_DATA_MB = "egressDataMb", DOMAINS = "domains", REMOTE_EXIT_NODES = "remoteExitNodes", + ORGINIZATIONS = "organizations", TIER1 = "tier1" } @@ -19,6 +20,8 @@ export async function getFeatureDisplayName(featureId: FeatureId): Promise; -export const sandboxLimitSet: LimitSet = { - [FeatureId.USERS]: { value: 1, description: "Sandbox limit" }, - [FeatureId.SITES]: { value: 1, description: "Sandbox limit" }, - [FeatureId.DOMAINS]: { value: 0, description: "Sandbox limit" }, - [FeatureId.REMOTE_EXIT_NODES]: { value: 0, description: "Sandbox limit" }, -}; - export const freeLimitSet: LimitSet = { [FeatureId.SITES]: { value: 5, description: "Basic limit" }, [FeatureId.USERS]: { value: 5, description: "Basic limit" }, [FeatureId.DOMAINS]: { value: 5, description: "Basic limit" }, [FeatureId.REMOTE_EXIT_NODES]: { value: 1, description: "Basic limit" }, + [FeatureId.ORGINIZATIONS]: { value: 1, description: "Basic limit" }, }; export const tier1LimitSet: LimitSet = { @@ -26,6 +20,7 @@ export const tier1LimitSet: LimitSet = { [FeatureId.SITES]: { value: 10, description: "Home limit" }, [FeatureId.DOMAINS]: { value: 10, description: "Home limit" }, [FeatureId.REMOTE_EXIT_NODES]: { value: 1, description: "Home limit" }, + [FeatureId.ORGINIZATIONS]: { value: 1, description: "Home limit" }, }; export const tier2LimitSet: LimitSet = { @@ -45,6 +40,10 @@ export const tier2LimitSet: LimitSet = { value: 3, description: "Team limit" }, + [FeatureId.ORGINIZATIONS]: { + value: 1, + description: "Team limit" + } }; export const tier3LimitSet: LimitSet = { @@ -64,4 +63,8 @@ export const tier3LimitSet: LimitSet = { value: 20, description: "Business limit" }, + [FeatureId.ORGINIZATIONS]: { + value: 20, + description: "Business limit" + }, }; diff --git a/server/lib/billing/usageService.ts b/server/lib/billing/usageService.ts index 03fa42a8..cde6cd2a 100644 --- a/server/lib/billing/usageService.ts +++ b/server/lib/billing/usageService.ts @@ -1,12 +1,8 @@ import { eq, sql, and } from "drizzle-orm"; -import { v4 as uuidv4 } from "uuid"; -import { PutObjectCommand } from "@aws-sdk/client-s3"; import { db, usage, customers, - sites, - newts, limits, Usage, Limit, @@ -15,21 +11,9 @@ import { } from "@server/db"; import { FeatureId, getFeatureMeterId } from "./features"; import logger from "@server/logger"; -import { sendToClient } from "#dynamic/routers/ws"; import { build } from "@server/build"; -import { s3Client } from "@server/lib/s3"; import cache from "@server/lib/cache"; -interface StripeEvent { - identifier?: string; - timestamp: number; - event_name: string; - payload: { - value: number; - stripe_customer_id: string; - }; -} - export function noop() { if (build !== "saas") { return true; @@ -38,41 +22,11 @@ export function noop() { } export class UsageService { - // private bucketName: string | undefined; - // private events: StripeEvent[] = []; - // private lastUploadTime: number = Date.now(); - // private isUploading: boolean = false; constructor() { if (noop()) { return; } - - // this.bucketName = process.env.S3_BUCKET || undefined; - - // // Periodically check and upload events - // setInterval(() => { - // this.checkAndUploadEvents().catch((err) => { - // logger.error("Error in periodic event upload:", err); - // }); - // }, 30000); // every 30 seconds - - // // Handle graceful shutdown on SIGTERM - // process.on("SIGTERM", async () => { - // logger.info( - // "SIGTERM received, uploading events before shutdown..." - // ); - // await this.forceUpload(); - // logger.info("Events uploaded, proceeding with shutdown"); - // }); - - // // Handle SIGINT as well (Ctrl+C) - // process.on("SIGINT", async () => { - // logger.info("SIGINT received, uploading events before shutdown..."); - // await this.forceUpload(); - // logger.info("Events uploaded, proceeding with shutdown"); - // process.exit(0); - // }); } /** @@ -103,16 +57,6 @@ export class UsageService { while (attempt <= maxRetries) { try { - // // Get subscription data for this org (with caching) - // const customerId = await this.getCustomerId(orgIdToUse, featureId); - - // if (!customerId) { - // logger.warn( - // `No subscription data found for org ${orgIdToUse} and feature ${featureId}` - // ); - // return null; - // } - let usage; if (transaction) { usage = await this.internalAddUsage( @@ -132,11 +76,6 @@ export class UsageService { }); } - // Log event for Stripe - // if (privateConfig.getRawPrivateConfig().flags.usage_reporting) { - // await this.logStripeEvent(featureId, value, customerId); - // } - return usage || null; } catch (error: any) { // Check if this is a deadlock error @@ -191,13 +130,14 @@ export class UsageService { featureId, orgId, meterId, + instantaneousValue: value, latestValue: value, updatedAt: Math.floor(Date.now() / 1000) }) .onConflictDoUpdate({ target: usage.usageId, set: { - latestValue: sql`${usage.latestValue} + ${value}` + instantaneousValue: sql`${usage.instantaneousValue} + ${value}` } }) .returning(); @@ -228,17 +168,6 @@ export class UsageService { let orgIdToUse = await this.getBillingOrg(orgId); try { - // if (!customerId) { - // customerId = - // (await this.getCustomerId(orgIdToUse, featureId)) || undefined; - // if (!customerId) { - // logger.warn( - // `No subscription data found for org ${orgIdToUse} and feature ${featureId}` - // ); - // return; - // } - // } - // Truncate value to 11 decimal places if provided if (value !== undefined && value !== null) { value = this.truncateValue(value); @@ -523,114 +452,6 @@ export class UsageService { return hasExceededLimits; } - - // private async logStripeEvent( - // featureId: FeatureId, - // value: number, - // customerId: string - // ): Promise { - // // Truncate value to 11 decimal places before sending to Stripe - // const truncatedValue = this.truncateValue(value); - - // const event: StripeEvent = { - // identifier: uuidv4(), - // timestamp: Math.floor(new Date().getTime() / 1000), - // event_name: featureId, - // payload: { - // value: truncatedValue, - // stripe_customer_id: customerId - // } - // }; - - // this.addEventToMemory(event); - // await this.checkAndUploadEvents(); - // } - - // private addEventToMemory(event: StripeEvent): void { - // if (!this.bucketName) { - // logger.warn( - // "S3 bucket name is not configured, skipping event storage." - // ); - // return; - // } - // this.events.push(event); - // } - - // private async checkAndUploadEvents(): Promise { - // const now = Date.now(); - // const timeSinceLastUpload = now - this.lastUploadTime; - - // // Check if at least 1 minute has passed since last upload - // if (timeSinceLastUpload >= 60000 && this.events.length > 0) { - // await this.uploadEventsToS3(); - // } - // } - - // private async uploadEventsToS3(): Promise { - // if (!this.bucketName) { - // logger.warn( - // "S3 bucket name is not configured, skipping S3 upload." - // ); - // return; - // } - - // if (this.events.length === 0) { - // return; - // } - - // // Check if already uploading - // if (this.isUploading) { - // logger.debug("Already uploading events, skipping"); - // return; - // } - - // this.isUploading = true; - - // try { - // // Take a snapshot of current events and clear the array - // const eventsToUpload = [...this.events]; - // this.events = []; - // this.lastUploadTime = Date.now(); - - // const fileName = this.generateEventFileName(); - // const fileContent = JSON.stringify(eventsToUpload, null, 2); - - // // Upload to S3 - // const uploadCommand = new PutObjectCommand({ - // Bucket: this.bucketName, - // Key: fileName, - // Body: fileContent, - // ContentType: "application/json" - // }); - - // await s3Client.send(uploadCommand); - - // logger.info( - // `Uploaded ${fileName} to S3 with ${eventsToUpload.length} events` - // ); - // } catch (error) { - // logger.error("Failed to upload events to S3:", error); - // // Note: Events are lost if upload fails. In a production system, - // // you might want to add the events back to the array or implement retry logic - // } finally { - // this.isUploading = false; - // } - // } - - // private generateEventFileName(): string { - // const timestamp = new Date().toISOString().replace(/[:.]/g, "-"); - // const uuid = uuidv4().substring(0, 8); - // return `events-${timestamp}-${uuid}.json`; - // } - - // public async forceUpload(): Promise { - // if (this.events.length > 0) { - // // Force upload regardless of time - // this.lastUploadTime = 0; // Reset to force upload - // await this.uploadEventsToS3(); - // } - // } - } export const usageService = new UsageService(); diff --git a/server/lib/createUserAccountOrg.ts b/server/lib/createUserAccountOrg.ts deleted file mode 100644 index a40407d1..00000000 --- a/server/lib/createUserAccountOrg.ts +++ /dev/null @@ -1,206 +0,0 @@ -import { isValidCIDR } from "@server/lib/validators"; -import { getNextAvailableOrgSubnet } from "@server/lib/ip"; -import { - actions, - apiKeyOrg, - apiKeys, - db, - domains, - Org, - orgDomains, - orgs, - roleActions, - roles, - userOrgs -} from "@server/db"; -import { eq } from "drizzle-orm"; -import { defaultRoleAllowedActions } from "@server/routers/role"; -import { FeatureId, limitsService, sandboxLimitSet } from "@server/lib/billing"; -import { createCustomer } from "#dynamic/lib/billing"; -import { usageService } from "@server/lib/billing/usageService"; -import config from "@server/lib/config"; -import { generateCA } from "@server/private/lib/sshCA"; -import { encrypt } from "@server/lib/crypto"; - -export async function createUserAccountOrg( - userId: string, - userEmail: string -): Promise<{ - success: boolean; - org?: { - orgId: string; - name: string; - subnet: string; - }; - error?: string; -}> { - // const subnet = await getNextAvailableOrgSubnet(); - const orgId = "org_" + userId; - const name = `${userEmail}'s Organization`; - - // if (!isValidCIDR(subnet)) { - // return { - // success: false, - // error: "Invalid subnet format. Please provide a valid CIDR notation." - // }; - // } - - // // make sure the subnet is unique - // const subnetExists = await db - // .select() - // .from(orgs) - // .where(eq(orgs.subnet, subnet)) - // .limit(1); - - // if (subnetExists.length > 0) { - // return { success: false, error: `Subnet ${subnet} already exists` }; - // } - - // make sure the orgId is unique - const orgExists = await db - .select() - .from(orgs) - .where(eq(orgs.orgId, orgId)) - .limit(1); - - if (orgExists.length > 0) { - return { - success: false, - error: `Organization with ID ${orgId} already exists` - }; - } - - let error = ""; - let org: Org | null = null; - - await db.transaction(async (trx) => { - const allDomains = await trx - .select() - .from(domains) - .where(eq(domains.configManaged, true)); - - const utilitySubnet = config.getRawConfig().orgs.utility_subnet_group; - - // Generate SSH CA keys for the org - const ca = generateCA(`${orgId}-ca`); - const encryptionKey = config.getRawConfig().server.secret!; - const encryptedCaPrivateKey = encrypt(ca.privateKeyPem, encryptionKey); - - const newOrg = await trx - .insert(orgs) - .values({ - orgId, - name, - // subnet - subnet: "100.90.128.0/24", // TODO: this should not be hardcoded - or can it be the same in all orgs? - utilitySubnet: utilitySubnet, - createdAt: new Date().toISOString(), - sshCaPrivateKey: encryptedCaPrivateKey, - sshCaPublicKey: ca.publicKeyOpenSSH - }) - .returning(); - - if (newOrg.length === 0) { - error = "Failed to create organization"; - trx.rollback(); - return; - } - - org = newOrg[0]; - - // Create admin role within the same transaction - const [insertedRole] = await trx - .insert(roles) - .values({ - orgId: newOrg[0].orgId, - isAdmin: true, - name: "Admin", - description: "Admin role with the most permissions" - }) - .returning({ roleId: roles.roleId }); - - if (!insertedRole || !insertedRole.roleId) { - error = "Failed to create Admin role"; - trx.rollback(); - return; - } - - const roleId = insertedRole.roleId; - - // Get all actions and create role actions - const actionIds = await trx.select().from(actions).execute(); - - if (actionIds.length > 0) { - await trx.insert(roleActions).values( - actionIds.map((action) => ({ - roleId, - actionId: action.actionId, - orgId: newOrg[0].orgId - })) - ); - } - - if (allDomains.length) { - await trx.insert(orgDomains).values( - allDomains.map((domain) => ({ - orgId: newOrg[0].orgId, - domainId: domain.domainId - })) - ); - } - - await trx.insert(userOrgs).values({ - userId, - orgId: newOrg[0].orgId, - roleId: roleId, - isOwner: true - }); - - const memberRole = await trx - .insert(roles) - .values({ - name: "Member", - description: "Members can only view resources", - orgId - }) - .returning(); - - await trx.insert(roleActions).values( - defaultRoleAllowedActions.map((action) => ({ - roleId: memberRole[0].roleId, - actionId: action, - orgId - })) - ); - }); - - await limitsService.applyLimitSetToOrg(orgId, sandboxLimitSet); - - if (!org) { - return { success: false, error: "Failed to create org" }; - } - - if (error) { - return { - success: false, - error: `Failed to create org: ${error}` - }; - } - - // make sure we have the stripe customer - const customerId = await createCustomer(orgId, userEmail); - - if (customerId) { - await usageService.updateCount(orgId, FeatureId.USERS, 1, customerId); // Only 1 because we are crating the org - } - - return { - org: { - orgId, - name, - // subnet - subnet: "100.90.128.0/24" - }, - success: true - }; -} diff --git a/server/lib/deleteOrg.ts b/server/lib/deleteOrg.ts index 7295555d..856759ab 100644 --- a/server/lib/deleteOrg.ts +++ b/server/lib/deleteOrg.ts @@ -19,6 +19,8 @@ import { sendToClient } from "#dynamic/routers/ws"; import { deletePeer } from "@server/routers/gerbil/peers"; import { OlmErrorCodes } from "@server/routers/olm/error"; import { sendTerminateClient } from "@server/routers/client/terminate"; +import { usageService } from "./billing/usageService"; +import { FeatureId } from "./billing"; export type DeleteOrgByIdResult = { deletedNewtIds: string[]; @@ -74,9 +76,7 @@ export async function deleteOrgById( deletedNewtIds.push(deletedNewt.newtId); await trx .delete(newtSessions) - .where( - eq(newtSessions.newtId, deletedNewt.newtId) - ); + .where(eq(newtSessions.newtId, deletedNewt.newtId)); } } } @@ -137,6 +137,9 @@ export async function deleteOrgById( .where(inArray(domains.domainId, domainIdsToDelete)); } await trx.delete(resources).where(eq(resources.orgId, orgId)); + + await usageService.add(orgId, FeatureId.ORGINIZATIONS, -1, trx); // here we are decreasing the org count BEFORE deleting the org because we need to still be able to get the org to get the billing org inside of here + await trx.delete(orgs).where(eq(orgs.orgId, orgId)); }); @@ -155,15 +158,13 @@ export function sendTerminationMessages(result: DeleteOrgByIdResult): void { ); } for (const olmId of result.olmsToTerminate) { - sendTerminateClient( - 0, - OlmErrorCodes.TERMINATED_REKEYED, - olmId - ).catch((error) => { - logger.error( - "Failed to send termination message to olm:", - error - ); - }); + sendTerminateClient(0, OlmErrorCodes.TERMINATED_REKEYED, olmId).catch( + (error) => { + logger.error( + "Failed to send termination message to olm:", + error + ); + } + ); } } diff --git a/server/private/routers/billing/getOrgUsage.ts b/server/private/routers/billing/getOrgUsage.ts index cf4e7585..4c9f22f3 100644 --- a/server/private/routers/billing/getOrgUsage.ts +++ b/server/private/routers/billing/getOrgUsage.ts @@ -85,10 +85,14 @@ export async function getOrgUsage( orgId, FeatureId.REMOTE_EXIT_NODES ); - const egressData = await usageService.getUsage( + const organizations = await usageService.getUsage( orgId, - FeatureId.EGRESS_DATA_MB + FeatureId.ORGINIZATIONS ); + // const egressData = await usageService.getUsage( + // orgId, + // FeatureId.EGRESS_DATA_MB + // ); if (sites) { usageData.push(sites); @@ -96,15 +100,18 @@ export async function getOrgUsage( if (users) { usageData.push(users); } - if (egressData) { - usageData.push(egressData); - } + // if (egressData) { + // usageData.push(egressData); + // } if (domains) { usageData.push(domains); } if (remoteExitNodes) { usageData.push(remoteExitNodes); } + if (organizations) { + usageData.push(organizations); + } const orgLimits = await db .select() diff --git a/server/private/routers/remoteExitNode/createRemoteExitNode.ts b/server/private/routers/remoteExitNode/createRemoteExitNode.ts index 14541736..6d5b5ea6 100644 --- a/server/private/routers/remoteExitNode/createRemoteExitNode.ts +++ b/server/private/routers/remoteExitNode/createRemoteExitNode.ts @@ -12,7 +12,14 @@ */ import { NextFunction, Request, Response } from "express"; -import { db, exitNodes, exitNodeOrgs, ExitNode, ExitNodeOrg } from "@server/db"; +import { + db, + exitNodes, + exitNodeOrgs, + ExitNode, + ExitNodeOrg, + orgs +} from "@server/db"; import HttpCode from "@server/types/HttpCode"; import { z } from "zod"; import { remoteExitNodes } from "@server/db"; @@ -25,7 +32,7 @@ import { createRemoteExitNodeSession } from "#private/auth/sessions/remoteExitNo import { fromError } from "zod-validation-error"; import { hashPassword, verifyPassword } from "@server/auth/password"; import logger from "@server/logger"; -import { and, eq } from "drizzle-orm"; +import { and, eq, inArray, ne } from "drizzle-orm"; import { getNextAvailableSubnet } from "@server/lib/exitNodes"; import { usageService } from "@server/lib/billing/usageService"; import { FeatureId } from "@server/lib/billing"; @@ -169,7 +176,17 @@ export async function createRemoteExitNode( ); } - let numExitNodeOrgs: ExitNodeOrg[] | undefined; + const [org] = await db + .select() + .from(orgs) + .where(eq(orgs.orgId, orgId)) + .limit(1); + + if (!org) { + return next( + createHttpError(HttpCode.NOT_FOUND, "Organization not found") + ); + } await db.transaction(async (trx) => { if (!existingExitNode) { @@ -217,19 +234,43 @@ export async function createRemoteExitNode( }); } - numExitNodeOrgs = await trx - .select() - .from(exitNodeOrgs) - .where(eq(exitNodeOrgs.orgId, orgId)); - }); + // calculate if the node is in any other of the orgs before we count it as an add to the billing org + if (org.billingOrgId) { + const otherBillingOrgs = await trx + .select() + .from(orgs) + .where( + and( + eq(orgs.billingOrgId, org.billingOrgId), + ne(orgs.orgId, orgId) + ) + ); - if (numExitNodeOrgs) { - await usageService.updateCount( - orgId, - FeatureId.REMOTE_EXIT_NODES, - numExitNodeOrgs.length - ); - } + const billingOrgIds = otherBillingOrgs.map((o) => o.orgId); + + const orgsInBillingDomainThatTheNodeIsStillIn = await trx + .select() + .from(exitNodeOrgs) + .where( + and( + eq( + exitNodeOrgs.exitNodeId, + existingExitNode.exitNodeId + ), + inArray(exitNodeOrgs.orgId, billingOrgIds) + ) + ); + + if (orgsInBillingDomainThatTheNodeIsStillIn.length === 0) { + await usageService.add( + orgId, + FeatureId.REMOTE_EXIT_NODES, + 1, + trx + ); + } + } + }); const token = generateSessionToken(); await createRemoteExitNodeSession(token, remoteExitNodeId); diff --git a/server/private/routers/remoteExitNode/deleteRemoteExitNode.ts b/server/private/routers/remoteExitNode/deleteRemoteExitNode.ts index 8337f05d..6ff6841c 100644 --- a/server/private/routers/remoteExitNode/deleteRemoteExitNode.ts +++ b/server/private/routers/remoteExitNode/deleteRemoteExitNode.ts @@ -13,9 +13,9 @@ import { NextFunction, Request, Response } from "express"; import { z } from "zod"; -import { db, ExitNodeOrg, exitNodeOrgs, exitNodes } from "@server/db"; +import { db, ExitNodeOrg, exitNodeOrgs, exitNodes, orgs } from "@server/db"; import { remoteExitNodes } from "@server/db"; -import { and, count, eq } from "drizzle-orm"; +import { and, count, eq, inArray } from "drizzle-orm"; import response from "@server/lib/response"; import HttpCode from "@server/types/HttpCode"; import createHttpError from "http-errors"; @@ -50,7 +50,8 @@ export async function deleteRemoteExitNode( const [remoteExitNode] = await db .select() .from(remoteExitNodes) - .where(eq(remoteExitNodes.remoteExitNodeId, remoteExitNodeId)); + .where(eq(remoteExitNodes.remoteExitNodeId, remoteExitNodeId)) + .limit(1); if (!remoteExitNode) { return next( @@ -70,7 +71,17 @@ export async function deleteRemoteExitNode( ); } - let numExitNodeOrgs: ExitNodeOrg[] | undefined; + const [org] = await db.select().from(orgs).where(eq(orgs.orgId, orgId)); + + if (!org) { + return next( + createHttpError( + HttpCode.NOT_FOUND, + `Org with ID ${orgId} not found` + ) + ); + } + await db.transaction(async (trx) => { await trx .delete(exitNodeOrgs) @@ -81,38 +92,39 @@ export async function deleteRemoteExitNode( ) ); - const [remainingExitNodeOrgs] = await trx - .select({ count: count() }) - .from(exitNodeOrgs) - .where(eq(exitNodeOrgs.exitNodeId, remoteExitNode.exitNodeId!)); + // calculate if the user is in any other of the orgs before we count it as an remove to the billing org + if (org.billingOrgId) { + const otherBillingOrgs = await trx + .select() + .from(orgs) + .where(eq(orgs.billingOrgId, org.billingOrgId)); - if (remainingExitNodeOrgs.count === 0) { - await trx - .delete(remoteExitNodes) + const billingOrgIds = otherBillingOrgs.map((o) => o.orgId); + + const orgsInBillingDomainThatTheNodeIsStillIn = await trx + .select() + .from(exitNodeOrgs) .where( - eq(remoteExitNodes.remoteExitNodeId, remoteExitNodeId) + and( + eq( + exitNodeOrgs.exitNodeId, + remoteExitNode.exitNodeId! + ), + inArray(exitNodeOrgs.orgId, billingOrgIds) + ) ); - await trx - .delete(exitNodes) - .where( - eq(exitNodes.exitNodeId, remoteExitNode.exitNodeId!) + + if (orgsInBillingDomainThatTheNodeIsStillIn.length === 0) { + await usageService.add( + orgId, + FeatureId.REMOTE_EXIT_NODES, + -1, + trx ); + } } - - numExitNodeOrgs = await trx - .select() - .from(exitNodeOrgs) - .where(eq(exitNodeOrgs.orgId, orgId)); }); - if (numExitNodeOrgs) { - await usageService.updateCount( - orgId, - FeatureId.REMOTE_EXIT_NODES, - numExitNodeOrgs.length - ); - } - return response(res, { data: null, success: true, diff --git a/server/routers/domain/createOrgDomain.ts b/server/routers/domain/createOrgDomain.ts index 35fb305f..ceb61b25 100644 --- a/server/routers/domain/createOrgDomain.ts +++ b/server/routers/domain/createOrgDomain.ts @@ -148,7 +148,6 @@ export async function createOrgDomain( } } - let numOrgDomains: OrgDomains[] | undefined; let aRecords: CreateDomainResponse["aRecords"]; let cnameRecords: CreateDomainResponse["cnameRecords"]; let txtRecords: CreateDomainResponse["txtRecords"]; @@ -347,20 +346,9 @@ export async function createOrgDomain( await trx.insert(dnsRecords).values(recordsToInsert); } - numOrgDomains = await trx - .select() - .from(orgDomains) - .where(eq(orgDomains.orgId, orgId)); + await usageService.add(orgId, FeatureId.DOMAINS, 1, trx); }); - if (numOrgDomains) { - await usageService.updateCount( - orgId, - FeatureId.DOMAINS, - numOrgDomains.length - ); - } - if (!returned) { return next( createHttpError( diff --git a/server/routers/domain/deleteOrgDomain.ts b/server/routers/domain/deleteOrgDomain.ts index 04829a13..4c347668 100644 --- a/server/routers/domain/deleteOrgDomain.ts +++ b/server/routers/domain/deleteOrgDomain.ts @@ -36,8 +36,6 @@ export async function deleteAccountDomain( } const { domainId, orgId } = parsed.data; - let numOrgDomains: OrgDomains[] | undefined; - await db.transaction(async (trx) => { const [existing] = await trx .select() @@ -79,20 +77,9 @@ export async function deleteAccountDomain( await trx.delete(domains).where(eq(domains.domainId, domainId)); - numOrgDomains = await trx - .select() - .from(orgDomains) - .where(eq(orgDomains.orgId, orgId)); + await usageService.add(orgId, FeatureId.DOMAINS, -1, trx); }); - if (numOrgDomains) { - await usageService.updateCount( - orgId, - FeatureId.DOMAINS, - numOrgDomains.length - ); - } - return response(res, { data: { success: true }, success: true, diff --git a/server/routers/org/createOrg.ts b/server/routers/org/createOrg.ts index 0c135e5b..45771492 100644 --- a/server/routers/org/createOrg.ts +++ b/server/routers/org/createOrg.ts @@ -1,7 +1,7 @@ import { Request, Response, NextFunction } from "express"; import { z } from "zod"; import { db } from "@server/db"; -import { and, eq } from "drizzle-orm"; +import { and, count, eq } from "drizzle-orm"; import { domains, Org, @@ -24,11 +24,7 @@ import { OpenAPITags, registry } from "@server/openApi"; import { isValidCIDR } from "@server/lib/validators"; import { createCustomer } from "#dynamic/lib/billing"; import { usageService } from "@server/lib/billing/usageService"; -import { - FeatureId, - limitsService, - sandboxLimitSet -} from "@server/lib/billing"; +import { FeatureId, limitsService, freeLimitSet } from "@server/lib/billing"; import { build } from "@server/build"; import { calculateUserClientsForOrgs } from "@server/lib/calculateUserClientsForOrgs"; import { doCidrsOverlap } from "@server/lib/ip"; @@ -114,6 +110,7 @@ export async function createOrg( // ) // ); // } + // // make sure the orgId is unique const orgExists = await db @@ -174,8 +171,46 @@ export async function createOrg( } } + if (build == "saas") { + if (!billingOrgIdForNewOrg) { + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "Billing org not found for user. Cannot create new organization." + ) + ); + } + + const usage = await usageService.getUsage(billingOrgIdForNewOrg, FeatureId.ORGINIZATIONS); + if (!usage) { + return next( + createHttpError( + HttpCode.NOT_FOUND, + "No usage data found for this organization" + ) + ); + } + const rejectOrgs = await usageService.checkLimitSet( + billingOrgIdForNewOrg, + FeatureId.ORGINIZATIONS, + { + ...usage, + instantaneousValue: (usage.instantaneousValue || 0) + 1 + } // We need to add one to know if we are violating the limit + ); + if (rejectOrgs) { + return next( + createHttpError( + HttpCode.FORBIDDEN, + "Organization limit exceeded. Please upgrade your plan." + ) + ); + } + } + let error = ""; let org: Org | null = null; + let numOrgs: number | null = null; await db.transaction(async (trx) => { const allDomains = await trx @@ -184,14 +219,14 @@ export async function createOrg( .where(eq(domains.configManaged, true)); // Generate SSH CA keys for the org - const ca = generateCA(`${orgId}-ca`); - const encryptionKey = config.getRawConfig().server.secret!; - const encryptedCaPrivateKey = encrypt(ca.privateKeyPem, encryptionKey); + // const ca = generateCA(`${orgId}-ca`); + // const encryptionKey = config.getRawConfig().server.secret!; + // const encryptedCaPrivateKey = encrypt(ca.privateKeyPem, encryptionKey); const saasBillingFields = build === "saas" && req.user && isFirstOrg !== null ? isFirstOrg - ? { isBillingOrg: true as const, billingOrgId: null } + ? { isBillingOrg: true as const, billingOrgId: orgId } // if this is the first org, it becomes the billing org for itself : { isBillingOrg: false as const, billingOrgId: billingOrgIdForNewOrg @@ -206,8 +241,8 @@ export async function createOrg( subnet, utilitySubnet, createdAt: new Date().toISOString(), - sshCaPrivateKey: encryptedCaPrivateKey, - sshCaPublicKey: ca.publicKeyOpenSSH, + // sshCaPrivateKey: encryptedCaPrivateKey, + // sshCaPublicKey: ca.publicKeyOpenSSH, ...saasBillingFields }) .returning(); @@ -310,6 +345,17 @@ export async function createOrg( ); await calculateUserClientsForOrgs(ownerUserId, trx); + + if (billingOrgIdForNewOrg) { + const [numOrgsResult] = await trx + .select({ count: count() }) + .from(orgs) + .where(eq(orgs.billingOrgId, billingOrgIdForNewOrg)); // all the billable orgs including the primary org that is the billing org itself + + numOrgs = numOrgsResult.count; + } else { + numOrgs = 1; // we only have one org if there is no billing org found out + } }); if (!org) { @@ -326,7 +372,7 @@ export async function createOrg( } if (build === "saas" && isFirstOrg === true) { - await limitsService.applyLimitSetToOrg(orgId, sandboxLimitSet); + await limitsService.applyLimitSetToOrg(orgId, freeLimitSet); const customerId = await createCustomer(orgId, req.user?.email); if (customerId) { await usageService.updateCount( @@ -338,6 +384,14 @@ export async function createOrg( } } + if (numOrgs) { + usageService.updateCount( + billingOrgIdForNewOrg || orgId, + FeatureId.ORGINIZATIONS, + numOrgs + ); + } + return response(res, { data: org, success: true, diff --git a/server/routers/site/createSite.ts b/server/routers/site/createSite.ts index 797bf2ae..ea4bc3e8 100644 --- a/server/routers/site/createSite.ts +++ b/server/routers/site/createSite.ts @@ -6,7 +6,7 @@ import response from "@server/lib/response"; import HttpCode from "@server/types/HttpCode"; import createHttpError from "http-errors"; import logger from "@server/logger"; -import { eq, and } from "drizzle-orm"; +import { eq, and, count } from "drizzle-orm"; import { getUniqueSiteName } from "../../db/names"; import { addPeer } from "../gerbil/peers"; import { fromError } from "zod-validation-error"; @@ -288,7 +288,6 @@ export async function createSite( const niceId = await getUniqueSiteName(orgId); let newSite: Site | undefined; - let numSites: Site[] | undefined; await db.transaction(async (trx) => { if (type == "newt") { [newSite] = await trx @@ -443,20 +442,9 @@ export async function createSite( }); } - numSites = await trx - .select() - .from(sites) - .where(eq(sites.orgId, orgId)); + await usageService.add(orgId, FeatureId.SITES, 1, trx); }); - if (numSites) { - await usageService.updateCount( - orgId, - FeatureId.SITES, - numSites.length - ); - } - if (!newSite) { return next( createHttpError( diff --git a/server/routers/site/deleteSite.ts b/server/routers/site/deleteSite.ts index 2ce900fd..cdb9d3ba 100644 --- a/server/routers/site/deleteSite.ts +++ b/server/routers/site/deleteSite.ts @@ -64,7 +64,6 @@ export async function deleteSite( } let deletedNewtId: string | null = null; - let numSites: Site[] | undefined; await db.transaction(async (trx) => { if (site.type == "wireguard") { @@ -101,21 +100,9 @@ export async function deleteSite( } } - await trx.delete(sites).where(eq(sites.siteId, siteId)); - - numSites = await trx - .select() - .from(sites) - .where(eq(sites.orgId, site.orgId)); + await usageService.add(site.orgId, FeatureId.SITES, -1, trx); }); - if (numSites) { - await usageService.updateCount( - site.orgId, - FeatureId.SITES, - numSites.length - ); - } // Send termination message outside of transaction to prevent blocking if (deletedNewtId) { const payload = { diff --git a/server/routers/user/acceptInvite.ts b/server/routers/user/acceptInvite.ts index 74f025ae..99a609a1 100644 --- a/server/routers/user/acceptInvite.ts +++ b/server/routers/user/acceptInvite.ts @@ -1,8 +1,8 @@ import { Request, Response, NextFunction } from "express"; import { z } from "zod"; -import { db, UserOrg } from "@server/db"; +import { db, orgs, UserOrg } from "@server/db"; import { roles, userInvites, userOrgs, users } from "@server/db"; -import { eq } from "drizzle-orm"; +import { eq, and, inArray, ne } from "drizzle-orm"; import response from "@server/lib/response"; import HttpCode from "@server/types/HttpCode"; import createHttpError from "http-errors"; @@ -125,8 +125,22 @@ export async function acceptInvite( } } + const [org] = await db + .select() + .from(orgs) + .where(eq(orgs.orgId, existingInvite.orgId)) + .limit(1); + + if (!org) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + "Organization does not exist. Please contact an admin." + ) + ); + } + let roleId: number; - let totalUsers: UserOrg[] | undefined; // get the role to make sure it exists const existingRole = await db .select() @@ -160,25 +174,45 @@ export async function acceptInvite( await calculateUserClientsForOrgs(existingUser[0].userId, trx); - // Get the total number of users in the org now - totalUsers = await trx - .select() - .from(userOrgs) - .where(eq(userOrgs.orgId, existingInvite.orgId)); + // calculate if the user is in any other of the orgs before we count it as an add to the billing org + if (org.billingOrgId) { + const otherBillingOrgs = await trx + .select() + .from(orgs) + .where( + and( + eq(orgs.billingOrgId, org.billingOrgId), + ne(orgs.orgId, existingInvite.orgId) + ) + ); + + const billingOrgIds = otherBillingOrgs.map((o) => o.orgId); + + const orgsInBillingDomainThatTheUserIsStillIn = await trx + .select() + .from(userOrgs) + .where( + and( + eq(userOrgs.userId, existingUser[0].userId), + inArray(userOrgs.orgId, billingOrgIds) + ) + ); + + if (orgsInBillingDomainThatTheUserIsStillIn.length === 0) { + await usageService.add( + existingInvite.orgId, + FeatureId.USERS, + 1, + trx + ); + } + } logger.debug( - `User ${existingUser[0].userId} accepted invite to org ${existingInvite.orgId}. Total users in org: ${totalUsers.length}` + `User ${existingUser[0].userId} accepted invite to org ${existingInvite.orgId}` ); }); - if (totalUsers) { - await usageService.updateCount( - existingInvite.orgId, - FeatureId.USERS, - totalUsers.length - ); - } - return response(res, { data: { accepted: true, orgId: existingInvite.orgId }, success: true, diff --git a/server/routers/user/createOrgUser.ts b/server/routers/user/createOrgUser.ts index d0515e71..f9cab25e 100644 --- a/server/routers/user/createOrgUser.ts +++ b/server/routers/user/createOrgUser.ts @@ -6,8 +6,8 @@ import createHttpError from "http-errors"; import logger from "@server/logger"; import { fromError } from "zod-validation-error"; import { OpenAPITags, registry } from "@server/openApi"; -import { db, UserOrg } from "@server/db"; -import { and, eq } from "drizzle-orm"; +import { db, orgs, UserOrg } from "@server/db"; +import { and, eq, inArray, ne } from "drizzle-orm"; import { idp, idpOidcConfig, roles, userOrgs, users } from "@server/db"; import { generateId } from "@server/auth/sessions/app"; import { usageService } from "@server/lib/billing/usageService"; @@ -151,6 +151,21 @@ export async function createOrgUser( ); } + const [org] = await db + .select() + .from(orgs) + .where(eq(orgs.orgId, orgId)) + .limit(1); + + if (!org) { + return next( + createHttpError( + HttpCode.NOT_FOUND, + "Organization not found" + ) + ); + } + const [idpRes] = await db .select() .from(idp) @@ -172,8 +187,6 @@ export async function createOrgUser( ); } - let orgUsers: UserOrg[] | undefined; - await db.transaction(async (trx) => { const [existingUser] = await trx .select() @@ -244,22 +257,37 @@ export async function createOrgUser( .returning(); } - // List all of the users in the org - orgUsers = await trx - .select() - .from(userOrgs) - .where(eq(userOrgs.orgId, orgId)); - await calculateUserClientsForOrgs(userId, trx); - }); - if (orgUsers) { - await usageService.updateCount( - orgId, - FeatureId.USERS, - orgUsers.length - ); - } + // calculate if the user is in any other of the orgs before we count it as an add to the billing org + if (org.billingOrgId) { + const otherBillingOrgs = await trx + .select() + .from(orgs) + .where( + and( + eq(orgs.billingOrgId, org.billingOrgId), + ne(orgs.orgId, orgId) + ) + ); + + const billingOrgIds = otherBillingOrgs.map((o) => o.orgId); + + const orgsInBillingDomainThatTheUserIsStillIn = await trx + .select() + .from(userOrgs) + .where( + and( + eq(userOrgs.userId, userId), + inArray(userOrgs.orgId, billingOrgIds) + ) + ); + + if (orgsInBillingDomainThatTheUserIsStillIn.length === 0) { + await usageService.add(orgId, FeatureId.USERS, 1, trx); + } + } + }); } else { return next( createHttpError(HttpCode.BAD_REQUEST, "User type is required") diff --git a/server/routers/user/removeUserOrg.ts b/server/routers/user/removeUserOrg.ts index 768d5fff..d90d78c0 100644 --- a/server/routers/user/removeUserOrg.ts +++ b/server/routers/user/removeUserOrg.ts @@ -1,8 +1,16 @@ import { Request, Response, NextFunction } from "express"; import { z } from "zod"; -import { db, resources, sites, UserOrg } from "@server/db"; +import { + db, + orgs, + resources, + siteResources, + sites, + UserOrg, + userSiteResources +} from "@server/db"; import { userOrgs, userResources, users, userSites } from "@server/db"; -import { and, count, eq, exists } from "drizzle-orm"; +import { and, count, eq, exists, inArray } from "drizzle-orm"; import response from "@server/lib/response"; import HttpCode from "@server/types/HttpCode"; import createHttpError from "http-errors"; @@ -50,16 +58,16 @@ export async function removeUserOrg( const { userId, orgId } = parsedParams.data; // get the user first - const user = await db + const [user] = await db .select() .from(userOrgs) .where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, orgId))); - if (!user || user.length === 0) { + if (!user) { return next(createHttpError(HttpCode.NOT_FOUND, "User not found")); } - if (user[0].isOwner) { + if (user.isOwner) { return next( createHttpError( HttpCode.BAD_REQUEST, @@ -68,7 +76,17 @@ export async function removeUserOrg( ); } - let userCount: UserOrg[] | undefined; + const [org] = await db + .select() + .from(orgs) + .where(eq(orgs.orgId, orgId)) + .limit(1); + + if (!org) { + return next( + createHttpError(HttpCode.NOT_FOUND, "Organization not found") + ); + } await db.transaction(async (trx) => { await trx @@ -97,6 +115,26 @@ export async function removeUserOrg( ) ); + await db.delete(userSiteResources).where( + and( + eq(userSiteResources.userId, userId), + exists( + db + .select() + .from(siteResources) + .where( + and( + eq( + siteResources.siteResourceId, + userSiteResources.siteResourceId + ), + eq(siteResources.orgId, orgId) + ) + ) + ) + ) + ); + await db.delete(userSites).where( and( eq(userSites.userId, userId), @@ -114,11 +152,6 @@ export async function removeUserOrg( ) ); - userCount = await trx - .select() - .from(userOrgs) - .where(eq(userOrgs.orgId, orgId)); - // if (build === "saas") { // const [rootUser] = await trx // .select() @@ -137,15 +170,31 @@ export async function removeUserOrg( // } await calculateUserClientsForOrgs(userId, trx); - }); - if (userCount) { - await usageService.updateCount( - orgId, - FeatureId.USERS, - userCount.length - ); - } + // calculate if the user is in any other of the orgs before we count it as an remove to the billing org + if (org.billingOrgId) { + const billingOrgs = await trx + .select() + .from(orgs) + .where(eq(orgs.billingOrgId, org.billingOrgId)); + + const billingOrgIds = billingOrgs.map((o) => o.orgId); + + const orgsInBillingDomainThatTheUserIsStillIn = await trx + .select() + .from(userOrgs) + .where( + and( + eq(userOrgs.userId, userId), + inArray(userOrgs.orgId, billingOrgIds) + ) + ); + + if (orgsInBillingDomainThatTheUserIsStillIn.length === 0) { + await usageService.add(orgId, FeatureId.USERS, -1, trx); + } + } + }); return response(res, { data: null, diff --git a/src/app/[orgId]/settings/(private)/billing/page.tsx b/src/app/[orgId]/settings/(private)/billing/page.tsx index b108d461..5f608e55 100644 --- a/src/app/[orgId]/settings/(private)/billing/page.tsx +++ b/src/app/[orgId]/settings/(private)/billing/page.tsx @@ -110,37 +110,42 @@ const planOptions: PlanOption[] = [ // Tier limits mapping derived from limit sets const tierLimits: Record< Tier | "basic", - { users: number; sites: number; domains: number; remoteNodes: number } + { users: number; sites: number; domains: number; remoteNodes: number; organizations: number } > = { basic: { users: freeLimitSet[FeatureId.USERS]?.value ?? 0, sites: freeLimitSet[FeatureId.SITES]?.value ?? 0, domains: freeLimitSet[FeatureId.DOMAINS]?.value ?? 0, - remoteNodes: freeLimitSet[FeatureId.REMOTE_EXIT_NODES]?.value ?? 0 + remoteNodes: freeLimitSet[FeatureId.REMOTE_EXIT_NODES]?.value ?? 0, + organizations: freeLimitSet[FeatureId.ORGINIZATIONS]?.value ?? 0 }, tier1: { users: tier1LimitSet[FeatureId.USERS]?.value ?? 0, sites: tier1LimitSet[FeatureId.SITES]?.value ?? 0, domains: tier1LimitSet[FeatureId.DOMAINS]?.value ?? 0, - remoteNodes: tier1LimitSet[FeatureId.REMOTE_EXIT_NODES]?.value ?? 0 + remoteNodes: tier1LimitSet[FeatureId.REMOTE_EXIT_NODES]?.value ?? 0, + organizations: tier1LimitSet[FeatureId.ORGINIZATIONS]?.value ?? 0 }, tier2: { users: tier2LimitSet[FeatureId.USERS]?.value ?? 0, sites: tier2LimitSet[FeatureId.SITES]?.value ?? 0, domains: tier2LimitSet[FeatureId.DOMAINS]?.value ?? 0, - remoteNodes: tier2LimitSet[FeatureId.REMOTE_EXIT_NODES]?.value ?? 0 + remoteNodes: tier2LimitSet[FeatureId.REMOTE_EXIT_NODES]?.value ?? 0, + organizations: tier2LimitSet[FeatureId.ORGINIZATIONS]?.value ?? 0 }, tier3: { users: tier3LimitSet[FeatureId.USERS]?.value ?? 0, sites: tier3LimitSet[FeatureId.SITES]?.value ?? 0, domains: tier3LimitSet[FeatureId.DOMAINS]?.value ?? 0, - remoteNodes: tier3LimitSet[FeatureId.REMOTE_EXIT_NODES]?.value ?? 0 + remoteNodes: tier3LimitSet[FeatureId.REMOTE_EXIT_NODES]?.value ?? 0, + organizations: tier3LimitSet[FeatureId.ORGINIZATIONS]?.value ?? 0 }, enterprise: { users: 0, // Custom for enterprise sites: 0, // Custom for enterprise domains: 0, // Custom for enterprise - remoteNodes: 0 // Custom for enterprise + remoteNodes: 0, // Custom for enterprise + organizations: 0 // Custom for enterprise } }; @@ -179,6 +184,7 @@ export default function BillingPage() { const SITES = "sites"; const DOMAINS = "domains"; const REMOTE_EXIT_NODES = "remoteExitNodes"; + const ORGINIZATIONS = "organizations"; // Confirmation dialog state const [showConfirmDialog, setShowConfirmDialog] = useState(false); @@ -619,6 +625,16 @@ export default function BillingPage() { }); } + // Check organizations + const organizationsUsage = getUsageValue(ORGINIZATIONS); + if (limits.organizations > 0 && organizationsUsage > limits.organizations) { + violations.push({ + feature: "Organizations", + currentUsage: organizationsUsage, + newLimit: limits.organizations + }); + } + return violations; }; @@ -855,6 +871,41 @@ export default function BillingPage() { )} + + + {t("billingOrganizations") || + "Organizations"} + + + {isOverLimit(ORGINIZATIONS) ? ( + + + + + {getLimitValue(ORGINIZATIONS) ?? + t("billingUnlimited") ?? + "∞"}{" "} + {getLimitValue(ORGINIZATIONS) !== + null && "organizations"} + + + +

    {t("billingUsageExceedsLimit", { current: getUsageValue(ORGINIZATIONS), limit: getLimitValue(ORGINIZATIONS) ?? 0 }) || `Current usage (${getUsageValue(ORGINIZATIONS)}) exceeds limit (${getLimitValue(ORGINIZATIONS)})`}

    +
    +
    + ) : ( + <> + {getLimitValue(ORGINIZATIONS) ?? + t("billingUnlimited") ?? + "∞"}{" "} + {getLimitValue(ORGINIZATIONS) !== + null && "organizations"} + + )} +
    +
    {t("billingRemoteNodes") || From 831eb6325c6ec5e9df2686f546032066a1a7ab56 Mon Sep 17 00:00:00 2001 From: Owen Date: Tue, 17 Feb 2026 17:31:41 -0800 Subject: [PATCH 70/81] Centralize user functions --- server/lib/userOrg.ts | 142 +++++++++++++++++++++++++++ server/routers/user/createOrgUser.ts | 52 ++-------- server/routers/user/removeUserOrg.ts | 88 +---------------- 3 files changed, 153 insertions(+), 129 deletions(-) create mode 100644 server/lib/userOrg.ts diff --git a/server/lib/userOrg.ts b/server/lib/userOrg.ts new file mode 100644 index 00000000..6ed10039 --- /dev/null +++ b/server/lib/userOrg.ts @@ -0,0 +1,142 @@ +import { + db, + Org, + orgs, + resources, + siteResources, + sites, + Transaction, + UserOrg, + userOrgs, + userResources, + userSiteResources, + userSites +} from "@server/db"; +import { eq, and, inArray, ne, exists } from "drizzle-orm"; +import { usageService } from "@server/lib/billing/usageService"; +import { FeatureId } from "@server/lib/billing"; + +export async function assignUserToOrg( + org: Org, + values: typeof userOrgs.$inferInsert, + trx: Transaction | typeof db = db +) { + const [userOrg] = await trx.insert(userOrgs).values(values).returning(); + + // calculate if the user is in any other of the orgs before we count it as an add to the billing org + if (org.billingOrgId) { + const otherBillingOrgs = await trx + .select() + .from(orgs) + .where( + and( + eq(orgs.billingOrgId, org.billingOrgId), + ne(orgs.orgId, org.orgId) + ) + ); + + const billingOrgIds = otherBillingOrgs.map((o) => o.orgId); + + const orgsInBillingDomainThatTheUserIsStillIn = await trx + .select() + .from(userOrgs) + .where( + and( + eq(userOrgs.userId, userOrg.userId), + inArray(userOrgs.orgId, billingOrgIds) + ) + ); + + if (orgsInBillingDomainThatTheUserIsStillIn.length === 0) { + await usageService.add(org.orgId, FeatureId.USERS, 1, trx); + } + } +} + +export async function removeUserFromOrg( + org: Org, + userId: string, + trx: Transaction | typeof db = db +) { + await trx + .delete(userOrgs) + .where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, org.orgId))); + + await trx.delete(userResources).where( + and( + eq(userResources.userId, userId), + exists( + trx + .select() + .from(resources) + .where( + and( + eq(resources.resourceId, userResources.resourceId), + eq(resources.orgId, org.orgId) + ) + ) + ) + ) + ); + + await trx.delete(userSiteResources).where( + and( + eq(userSiteResources.userId, userId), + exists( + trx + .select() + .from(siteResources) + .where( + and( + eq( + siteResources.siteResourceId, + userSiteResources.siteResourceId + ), + eq(siteResources.orgId, org.orgId) + ) + ) + ) + ) + ); + + await trx.delete(userSites).where( + and( + eq(userSites.userId, userId), + exists( + db + .select() + .from(sites) + .where( + and( + eq(sites.siteId, userSites.siteId), + eq(sites.orgId, org.orgId) + ) + ) + ) + ) + ); + + // calculate if the user is in any other of the orgs before we count it as an remove to the billing org + if (org.billingOrgId) { + const billingOrgs = await trx + .select() + .from(orgs) + .where(eq(orgs.billingOrgId, org.billingOrgId)); + + const billingOrgIds = billingOrgs.map((o) => o.orgId); + + const orgsInBillingDomainThatTheUserIsStillIn = await trx + .select() + .from(userOrgs) + .where( + and( + eq(userOrgs.userId, userId), + inArray(userOrgs.orgId, billingOrgIds) + ) + ); + + if (orgsInBillingDomainThatTheUserIsStillIn.length === 0) { + await usageService.add(org.orgId, FeatureId.USERS, -1, trx); + } + } +} diff --git a/server/routers/user/createOrgUser.ts b/server/routers/user/createOrgUser.ts index f9cab25e..b39ea22e 100644 --- a/server/routers/user/createOrgUser.ts +++ b/server/routers/user/createOrgUser.ts @@ -16,6 +16,7 @@ import { build } from "@server/build"; import { calculateUserClientsForOrgs } from "@server/lib/calculateUserClientsForOrgs"; import { isSubscribed } from "#dynamic/lib/isSubscribed"; import { tierMatrix } from "@server/lib/billing/tierMatrix"; +import { assignUserToOrg } from "@server/lib/userOrg"; const paramsSchema = z.strictObject({ orgId: z.string().nonempty() @@ -220,15 +221,12 @@ export async function createOrgUser( ); } - await trx - .insert(userOrgs) - .values({ - orgId, - userId: existingUser.userId, - roleId: role.roleId, - autoProvisioned: false - }) - .returning(); + await assignUserToOrg(org, { + orgId, + userId: existingUser.userId, + roleId: role.roleId, + autoProvisioned: false + }, trx); } else { userId = generateId(15); @@ -246,47 +244,15 @@ export async function createOrgUser( }) .returning(); - await trx - .insert(userOrgs) - .values({ + await assignUserToOrg(org, { orgId, userId: newUser.userId, roleId: role.roleId, autoProvisioned: false - }) - .returning(); + }, trx); } await calculateUserClientsForOrgs(userId, trx); - - // calculate if the user is in any other of the orgs before we count it as an add to the billing org - if (org.billingOrgId) { - const otherBillingOrgs = await trx - .select() - .from(orgs) - .where( - and( - eq(orgs.billingOrgId, org.billingOrgId), - ne(orgs.orgId, orgId) - ) - ); - - const billingOrgIds = otherBillingOrgs.map((o) => o.orgId); - - const orgsInBillingDomainThatTheUserIsStillIn = await trx - .select() - .from(userOrgs) - .where( - and( - eq(userOrgs.userId, userId), - inArray(userOrgs.orgId, billingOrgIds) - ) - ); - - if (orgsInBillingDomainThatTheUserIsStillIn.length === 0) { - await usageService.add(orgId, FeatureId.USERS, 1, trx); - } - } }); } else { return next( diff --git a/server/routers/user/removeUserOrg.ts b/server/routers/user/removeUserOrg.ts index d90d78c0..4c321ad3 100644 --- a/server/routers/user/removeUserOrg.ts +++ b/server/routers/user/removeUserOrg.ts @@ -22,6 +22,7 @@ import { FeatureId } from "@server/lib/billing"; import { build } from "@server/build"; import { UserType } from "@server/types/UserTypes"; import { calculateUserClientsForOrgs } from "@server/lib/calculateUserClientsForOrgs"; +import { removeUserFromOrg } from "@server/lib/userOrg"; const removeUserSchema = z.strictObject({ userId: z.string(), @@ -89,68 +90,7 @@ export async function removeUserOrg( } await db.transaction(async (trx) => { - await trx - .delete(userOrgs) - .where( - and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, orgId)) - ); - - await db.delete(userResources).where( - and( - eq(userResources.userId, userId), - exists( - db - .select() - .from(resources) - .where( - and( - eq( - resources.resourceId, - userResources.resourceId - ), - eq(resources.orgId, orgId) - ) - ) - ) - ) - ); - - await db.delete(userSiteResources).where( - and( - eq(userSiteResources.userId, userId), - exists( - db - .select() - .from(siteResources) - .where( - and( - eq( - siteResources.siteResourceId, - userSiteResources.siteResourceId - ), - eq(siteResources.orgId, orgId) - ) - ) - ) - ) - ); - - await db.delete(userSites).where( - and( - eq(userSites.userId, userId), - exists( - db - .select() - .from(sites) - .where( - and( - eq(sites.siteId, userSites.siteId), - eq(sites.orgId, orgId) - ) - ) - ) - ) - ); + await removeUserFromOrg(org, userId, trx); // if (build === "saas") { // const [rootUser] = await trx @@ -170,30 +110,6 @@ export async function removeUserOrg( // } await calculateUserClientsForOrgs(userId, trx); - - // calculate if the user is in any other of the orgs before we count it as an remove to the billing org - if (org.billingOrgId) { - const billingOrgs = await trx - .select() - .from(orgs) - .where(eq(orgs.billingOrgId, org.billingOrgId)); - - const billingOrgIds = billingOrgs.map((o) => o.orgId); - - const orgsInBillingDomainThatTheUserIsStillIn = await trx - .select() - .from(userOrgs) - .where( - and( - eq(userOrgs.userId, userId), - inArray(userOrgs.orgId, billingOrgIds) - ) - ); - - if (orgsInBillingDomainThatTheUserIsStillIn.length === 0) { - await usageService.add(orgId, FeatureId.USERS, -1, trx); - } - } }); return response(res, { From 8a83e32c420107ea52d9182538b9614f9b83f58a Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Tue, 17 Feb 2026 17:33:24 -0800 Subject: [PATCH 71/81] add send email verification opt out --- server/routers/auth/signup.ts | 20 +++++++++++++++----- server/routers/idp/validateOidcCallback.ts | 1 + src/app/auth/signup/page.tsx | 5 +++++ src/components/SignupForm.tsx | 7 +++++-- 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/server/routers/auth/signup.ts b/server/routers/auth/signup.ts index c1c344d8..cf8e4141 100644 --- a/server/routers/auth/signup.ts +++ b/server/routers/auth/signup.ts @@ -1,7 +1,7 @@ import { NextFunction, Request, Response } from "express"; import { db, users } from "@server/db"; import HttpCode from "@server/types/HttpCode"; -import { z } from "zod"; +import { email, z } from "zod"; import { fromError } from "zod-validation-error"; import createHttpError from "http-errors"; import response from "@server/lib/response"; @@ -30,7 +30,8 @@ export const signupBodySchema = z.object({ inviteToken: z.string().optional(), inviteId: z.string().optional(), termsAcceptedTimestamp: z.string().nullable().optional(), - marketingEmailConsent: z.boolean().optional() + marketingEmailConsent: z.boolean().optional(), + skipVerificationEmail: z.boolean().optional() }); export type SignUpBody = z.infer; @@ -61,7 +62,8 @@ export async function signup( inviteToken, inviteId, termsAcceptedTimestamp, - marketingEmailConsent + marketingEmailConsent, + skipVerificationEmail } = parsedBody.data; const passwordHash = await hashPassword(password); @@ -214,7 +216,13 @@ export async function signup( } if (config.getRawConfig().flags?.require_email_verification) { - sendEmailVerificationCode(email, userId); + if (!skipVerificationEmail) { + sendEmailVerificationCode(email, userId); + } else { + logger.debug( + `User ${email} opted out of verification email during signup.` + ); + } return response(res, { data: { @@ -222,7 +230,9 @@ export async function signup( }, success: true, error: false, - message: `User created successfully. We sent an email to ${email} with a verification code.`, + message: skipVerificationEmail + ? "User created successfully. Please verify your email." + : `User created successfully. We sent an email to ${email} with a verification code.`, status: HttpCode.OK }); } diff --git a/server/routers/idp/validateOidcCallback.ts b/server/routers/idp/validateOidcCallback.ts index f4065c59..7d756a4a 100644 --- a/server/routers/idp/validateOidcCallback.ts +++ b/server/routers/idp/validateOidcCallback.ts @@ -436,6 +436,7 @@ export async function validateOidcCallback( } } + // These are the orgs that the user should be provisioned into based on the IdP mappings and the token claims logger.debug("User org info", { userOrgInfo }); let existingUserId = existingUser?.userId; diff --git a/src/app/auth/signup/page.tsx b/src/app/auth/signup/page.tsx index 9ab7b7e6..f51ac904 100644 --- a/src/app/auth/signup/page.tsx +++ b/src/app/auth/signup/page.tsx @@ -15,6 +15,7 @@ export default async function Page(props: { redirect: string | undefined; email: string | undefined; fromSmartLogin: string | undefined; + skipVerificationEmail: string | undefined; }>; }) { const searchParams = await props.searchParams; @@ -75,6 +76,10 @@ export default async function Page(props: { inviteId={inviteId} emailParam={searchParams.email} fromSmartLogin={searchParams.fromSmartLogin === "true"} + skipVerificationEmail={ + searchParams.skipVerificationEmail === "true" || + searchParams.skipVerificationEmail === "1" + } />

    diff --git a/src/components/SignupForm.tsx b/src/components/SignupForm.tsx index a54b1c23..23c7713e 100644 --- a/src/components/SignupForm.tsx +++ b/src/components/SignupForm.tsx @@ -72,6 +72,7 @@ type SignupFormProps = { inviteToken?: string; emailParam?: string; fromSmartLogin?: boolean; + skipVerificationEmail?: boolean; }; const formSchema = z @@ -103,7 +104,8 @@ export default function SignupForm({ inviteId, inviteToken, emailParam, - fromSmartLogin = false + fromSmartLogin = false, + skipVerificationEmail = false }: SignupFormProps) { const router = useRouter(); const { env } = useEnvContext(); @@ -147,7 +149,8 @@ export default function SignupForm({ inviteToken, termsAcceptedTimestamp: termsAgreedAt, marketingEmailConsent: - build === "saas" ? marketingEmailConsent : undefined + build === "saas" ? marketingEmailConsent : undefined, + skipVerificationEmail: skipVerificationEmail || undefined }) .catch((e) => { console.error(e); From e370f8891a72c2bf2e2774ae833cfa50def8054e Mon Sep 17 00:00:00 2001 From: Owen Date: Tue, 17 Feb 2026 17:34:39 -0800 Subject: [PATCH 72/81] Also update in the assign --- server/routers/user/acceptInvite.ts | 50 ++++++----------------------- 1 file changed, 10 insertions(+), 40 deletions(-) diff --git a/server/routers/user/acceptInvite.ts b/server/routers/user/acceptInvite.ts index 99a609a1..388db4a3 100644 --- a/server/routers/user/acceptInvite.ts +++ b/server/routers/user/acceptInvite.ts @@ -14,6 +14,7 @@ import { usageService } from "@server/lib/billing/usageService"; import { FeatureId } from "@server/lib/billing"; import { calculateUserClientsForOrgs } from "@server/lib/calculateUserClientsForOrgs"; import { build } from "@server/build"; +import { assignUserToOrg } from "@server/lib/userOrg"; const acceptInviteBodySchema = z.strictObject({ token: z.string(), @@ -160,12 +161,15 @@ export async function acceptInvite( } await db.transaction(async (trx) => { - // add the user to the org - await trx.insert(userOrgs).values({ - userId: existingUser[0].userId, - orgId: existingInvite.orgId, - roleId: existingInvite.roleId - }); + await assignUserToOrg( + org, + { + userId: existingUser[0].userId, + orgId: existingInvite.orgId, + roleId: existingInvite.roleId + }, + trx + ); // delete the invite await trx @@ -174,40 +178,6 @@ export async function acceptInvite( await calculateUserClientsForOrgs(existingUser[0].userId, trx); - // calculate if the user is in any other of the orgs before we count it as an add to the billing org - if (org.billingOrgId) { - const otherBillingOrgs = await trx - .select() - .from(orgs) - .where( - and( - eq(orgs.billingOrgId, org.billingOrgId), - ne(orgs.orgId, existingInvite.orgId) - ) - ); - - const billingOrgIds = otherBillingOrgs.map((o) => o.orgId); - - const orgsInBillingDomainThatTheUserIsStillIn = await trx - .select() - .from(userOrgs) - .where( - and( - eq(userOrgs.userId, existingUser[0].userId), - inArray(userOrgs.orgId, billingOrgIds) - ) - ); - - if (orgsInBillingDomainThatTheUserIsStillIn.length === 0) { - await usageService.add( - existingInvite.orgId, - FeatureId.USERS, - 1, - trx - ); - } - } - logger.debug( `User ${existingUser[0].userId} accepted invite to org ${existingInvite.orgId}` ); From a2ed22bfccd00229dccb5f9d4d3c29f783a5a5f3 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Tue, 17 Feb 2026 17:50:23 -0800 Subject: [PATCH 73/81] use add/remove helper functions in auto (de)provision --- server/routers/idp/validateOidcCallback.ts | 73 +++++++++++++++------- 1 file changed, 51 insertions(+), 22 deletions(-) diff --git a/server/routers/idp/validateOidcCallback.ts b/server/routers/idp/validateOidcCallback.ts index 7d756a4a..e3462185 100644 --- a/server/routers/idp/validateOidcCallback.ts +++ b/server/routers/idp/validateOidcCallback.ts @@ -36,6 +36,10 @@ import { build } from "@server/build"; import { calculateUserClientsForOrgs } from "@server/lib/calculateUserClientsForOrgs"; import { isSubscribed } from "#dynamic/lib/isSubscribed"; import { tierMatrix } from "@server/lib/billing/tierMatrix"; +import { + assignUserToOrg, + removeUserFromOrg +} from "@server/lib/userOrg"; const ensureTrailingSlash = (url: string): string => { return url; @@ -455,15 +459,32 @@ export async function validateOidcCallback( ); if (!existingUserOrgs.length) { - // delete all auto -provisioned user orgs - await db - .delete(userOrgs) + // delete all auto-provisioned user orgs + const autoProvisionedUserOrgs = await db + .select() + .from(userOrgs) .where( and( eq(userOrgs.userId, existingUser.userId), eq(userOrgs.autoProvisioned, true) ) ); + const orgIdsToRemove = autoProvisionedUserOrgs.map( + (uo) => uo.orgId + ); + if (orgIdsToRemove.length > 0) { + const orgsToRemove = await db + .select() + .from(orgs) + .where(inArray(orgs.orgId, orgIdsToRemove)); + for (const org of orgsToRemove) { + await removeUserFromOrg( + org, + existingUser.userId, + db + ); + } + } await calculateUserClientsForOrgs(existingUser.userId); @@ -485,7 +506,7 @@ export async function validateOidcCallback( } } - const orgUserCounts: { orgId: string; userCount: number }[] = []; + const orgUserCounts: { orgId: string; userCount: number }[] = []; // sync the user with the orgs and roles await db.transaction(async (trx) => { @@ -539,15 +560,14 @@ export async function validateOidcCallback( ); if (orgsToDelete.length > 0) { - await trx.delete(userOrgs).where( - and( - eq(userOrgs.userId, userId!), - inArray( - userOrgs.orgId, - orgsToDelete.map((org) => org.orgId) - ) - ) - ); + const orgIdsToRemove = orgsToDelete.map((org) => org.orgId); + const fullOrgsToRemove = await trx + .select() + .from(orgs) + .where(inArray(orgs.orgId, orgIdsToRemove)); + for (const org of fullOrgsToRemove) { + await removeUserFromOrg(org, userId!, trx); + } } // Update roles for existing auto-provisioned orgs where the role has changed @@ -588,15 +608,24 @@ export async function validateOidcCallback( ); if (orgsToAdd.length > 0) { - await trx.insert(userOrgs).values( - orgsToAdd.map((org) => ({ - userId: userId!, - orgId: org.orgId, - roleId: org.roleId, - autoProvisioned: true, - dateCreated: new Date().toISOString() - })) - ); + for (const org of orgsToAdd) { + const [fullOrg] = await trx + .select() + .from(orgs) + .where(eq(orgs.orgId, org.orgId)); + if (fullOrg) { + await assignUserToOrg( + fullOrg, + { + orgId: org.orgId, + userId: userId!, + roleId: org.roleId, + autoProvisioned: true, + }, + trx + ); + } + } } // Loop through all the orgs and get the total number of users from the userOrgs table From 6661a76aa801e324e47482809824d544e7ffb3a3 Mon Sep 17 00:00:00 2001 From: Owen Date: Tue, 17 Feb 2026 20:01:15 -0800 Subject: [PATCH 74/81] Update member resources page and testing new org counts --- messages/en-US.json | 1 + server/lib/billing/usageService.ts | 10 +- server/routers/org/createOrg.ts | 11 +- server/routers/resource/getUserResources.ts | 113 ++++++++- server/routers/site/deleteSite.ts | 2 + .../settings/(private)/billing/page.tsx | 10 +- src/components/MemberResourcesPortal.tsx | 239 +++++++++++++++++- 7 files changed, 347 insertions(+), 39 deletions(-) diff --git a/messages/en-US.json b/messages/en-US.json index ecef7605..35e88375 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -1428,6 +1428,7 @@ "billingSites": "Sites", "billingUsers": "Users", "billingDomains": "Domains", + "billingOrganizations": "Orgs", "billingRemoteExitNodes": "Remote Nodes", "billingNoLimitConfigured": "No limit configured", "billingEstimatedPeriod": "Estimated Billing Period", diff --git a/server/lib/billing/usageService.ts b/server/lib/billing/usageService.ts index cde6cd2a..a7786c76 100644 --- a/server/lib/billing/usageService.ts +++ b/server/lib/billing/usageService.ts @@ -130,18 +130,22 @@ export class UsageService { featureId, orgId, meterId, - instantaneousValue: value, - latestValue: value, + instantaneousValue: value || 0, + latestValue: value || 0, updatedAt: Math.floor(Date.now() / 1000) }) .onConflictDoUpdate({ target: usage.usageId, set: { - instantaneousValue: sql`${usage.instantaneousValue} + ${value}` + instantaneousValue: sql`COALESCE(${usage.instantaneousValue}, 0) + ${value}` } }) .returning(); + logger.debug( + `Added usage for org ${orgId} feature ${featureId}: +${value}, new instantaneousValue: ${returnUsage.instantaneousValue}` + ); + return returnUsage; } diff --git a/server/routers/org/createOrg.ts b/server/routers/org/createOrg.ts index 45771492..e58f8207 100644 --- a/server/routers/org/createOrg.ts +++ b/server/routers/org/createOrg.ts @@ -171,16 +171,7 @@ export async function createOrg( } } - if (build == "saas") { - if (!billingOrgIdForNewOrg) { - return next( - createHttpError( - HttpCode.INTERNAL_SERVER_ERROR, - "Billing org not found for user. Cannot create new organization." - ) - ); - } - + if (build == "saas" && billingOrgIdForNewOrg) { const usage = await usageService.getUsage(billingOrgIdForNewOrg, FeatureId.ORGINIZATIONS); if (!usage) { return next( diff --git a/server/routers/resource/getUserResources.ts b/server/routers/resource/getUserResources.ts index 3d28da6f..eb5f8a8d 100644 --- a/server/routers/resource/getUserResources.ts +++ b/server/routers/resource/getUserResources.ts @@ -8,7 +8,10 @@ import { userOrgs, resourcePassword, resourcePincode, - resourceWhitelist + resourceWhitelist, + siteResources, + userSiteResources, + roleSiteResources } from "@server/db"; import createHttpError from "http-errors"; import HttpCode from "@server/types/HttpCode"; @@ -57,9 +60,21 @@ export async function getUserResources( .from(roleResources) .where(eq(roleResources.roleId, userRoleId)); - const [directResources, roleResourceResults] = await Promise.all([ + const directSiteResourcesQuery = db + .select({ siteResourceId: userSiteResources.siteResourceId }) + .from(userSiteResources) + .where(eq(userSiteResources.userId, userId)); + + const roleSiteResourcesQuery = db + .select({ siteResourceId: roleSiteResources.siteResourceId }) + .from(roleSiteResources) + .where(eq(roleSiteResources.roleId, userRoleId)); + + const [directResources, roleResourceResults, directSiteResourceResults, roleSiteResourceResults] = await Promise.all([ directResourcesQuery, - roleResourcesQuery + roleResourcesQuery, + directSiteResourcesQuery, + roleSiteResourcesQuery ]); // Combine all accessible resource IDs @@ -68,18 +83,25 @@ export async function getUserResources( ...roleResourceResults.map((r) => r.resourceId) ]; - if (accessibleResourceIds.length === 0) { - return response(res, { - data: { resources: [] }, - success: true, - error: false, - message: "No resources found", - status: HttpCode.OK - }); - } + // Combine all accessible site resource IDs + const accessibleSiteResourceIds = [ + ...directSiteResourceResults.map((r) => r.siteResourceId), + ...roleSiteResourceResults.map((r) => r.siteResourceId) + ]; // Get resource details for accessible resources - const resourcesData = await db + let resourcesData: Array<{ + resourceId: number; + name: string; + fullDomain: string | null; + ssl: boolean; + enabled: boolean; + sso: boolean; + protocol: string; + emailWhitelistEnabled: boolean; + }> = []; + if (accessibleResourceIds.length > 0) { + resourcesData = await db .select({ resourceId: resources.resourceId, name: resources.name, @@ -98,6 +120,40 @@ export async function getUserResources( eq(resources.enabled, true) ) ); + } + + // Get site resource details for accessible site resources + let siteResourcesData: Array<{ + siteResourceId: number; + name: string; + destination: string; + mode: string; + protocol: string | null; + enabled: boolean; + alias: string | null; + aliasAddress: string | null; + }> = []; + if (accessibleSiteResourceIds.length > 0) { + siteResourcesData = await db + .select({ + siteResourceId: siteResources.siteResourceId, + name: siteResources.name, + destination: siteResources.destination, + mode: siteResources.mode, + protocol: siteResources.protocol, + enabled: siteResources.enabled, + alias: siteResources.alias, + aliasAddress: siteResources.aliasAddress + }) + .from(siteResources) + .where( + and( + inArray(siteResources.siteResourceId, accessibleSiteResourceIds), + eq(siteResources.orgId, orgId), + eq(siteResources.enabled, true) + ) + ); + } // Check for password, pincode, and whitelist protection for each resource const resourcesWithAuth = await Promise.all( @@ -161,8 +217,26 @@ export async function getUserResources( }) ); + // Format site resources + const siteResourcesFormatted = siteResourcesData.map((siteResource) => { + return { + siteResourceId: siteResource.siteResourceId, + name: siteResource.name, + destination: siteResource.destination, + mode: siteResource.mode, + protocol: siteResource.protocol, + enabled: siteResource.enabled, + alias: siteResource.alias, + aliasAddress: siteResource.aliasAddress, + type: 'site' as const + }; + }); + return response(res, { - data: { resources: resourcesWithAuth }, + data: { + resources: resourcesWithAuth, + siteResources: siteResourcesFormatted + }, success: true, error: false, message: "User resources retrieved successfully", @@ -190,5 +264,16 @@ export type GetUserResourcesResponse = { protected: boolean; protocol: string; }>; + siteResources: Array<{ + siteResourceId: number; + name: string; + destination: string; + mode: string; + protocol: string | null; + enabled: boolean; + alias: string | null; + aliasAddress: string | null; + type: 'site'; + }>; }; }; diff --git a/server/routers/site/deleteSite.ts b/server/routers/site/deleteSite.ts index cdb9d3ba..58757253 100644 --- a/server/routers/site/deleteSite.ts +++ b/server/routers/site/deleteSite.ts @@ -100,6 +100,8 @@ export async function deleteSite( } } + await trx.delete(sites).where(eq(sites.siteId, siteId)); + await usageService.add(site.orgId, FeatureId.SITES, -1, trx); }); diff --git a/src/app/[orgId]/settings/(private)/billing/page.tsx b/src/app/[orgId]/settings/(private)/billing/page.tsx index 5f608e55..9d672902 100644 --- a/src/app/[orgId]/settings/(private)/billing/page.tsx +++ b/src/app/[orgId]/settings/(private)/billing/page.tsx @@ -768,7 +768,7 @@ export default function BillingPage() {

    {t("billingMaximumLimits") || "Maximum Limits"}
    - + {t("billingUsers") || "Users"} @@ -888,7 +888,7 @@ export default function BillingPage() { t("billingUnlimited") ?? "∞"}{" "} {getLimitValue(ORGINIZATIONS) !== - null && "organizations"} + null && "orgs"}
    @@ -901,7 +901,7 @@ export default function BillingPage() { t("billingUnlimited") ?? "∞"}{" "} {getLimitValue(ORGINIZATIONS) !== - null && "organizations"} + null && "orgs"} )} @@ -923,7 +923,7 @@ export default function BillingPage() { t("billingUnlimited") ?? "∞"}{" "} {getLimitValue(REMOTE_EXIT_NODES) !== - null && "remote nodes"} + null && "nodes"} @@ -936,7 +936,7 @@ export default function BillingPage() { t("billingUnlimited") ?? "∞"}{" "} {getLimitValue(REMOTE_EXIT_NODES) !== - null && "remote nodes"} + null && "nodes"} )} diff --git a/src/components/MemberResourcesPortal.tsx b/src/components/MemberResourcesPortal.tsx index 4d3a7717..93456b12 100644 --- a/src/components/MemberResourcesPortal.tsx +++ b/src/components/MemberResourcesPortal.tsx @@ -58,6 +58,18 @@ type Resource = { siteName?: string | null; }; +type SiteResource = { + siteResourceId: number; + name: string; + destination: string; + mode: string; + protocol: string | null; + enabled: boolean; + alias: string | null; + aliasAddress: string | null; + type: 'site'; +}; + type MemberResourcesPortalProps = { orgId: string; }; @@ -334,7 +346,9 @@ export default function MemberResourcesPortal({ const { toast } = useToast(); const [resources, setResources] = useState([]); + const [siteResources, setSiteResources] = useState([]); const [filteredResources, setFilteredResources] = useState([]); + const [filteredSiteResources, setFilteredSiteResources] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [searchQuery, setSearchQuery] = useState(""); @@ -360,7 +374,9 @@ export default function MemberResourcesPortal({ if (response.data.success) { setResources(response.data.data.resources); + setSiteResources(response.data.data.siteResources || []); setFilteredResources(response.data.data.resources); + setFilteredSiteResources(response.data.data.siteResources || []); } else { setError("Failed to load resources"); } @@ -417,17 +433,61 @@ export default function MemberResourcesPortal({ setFilteredResources(filtered); + // Filter and sort site resources + const filteredSites = siteResources.filter( + (resource) => + resource.name + .toLowerCase() + .includes(searchQuery.toLowerCase()) || + resource.destination + .toLowerCase() + .includes(searchQuery.toLowerCase()) + ); + + // Sort site resources + filteredSites.sort((a, b) => { + switch (sortBy) { + case "name-asc": + return a.name.localeCompare(b.name); + case "name-desc": + return b.name.localeCompare(a.name); + case "domain-asc": + case "domain-desc": + // Sort by destination for site resources + const destCompare = sortBy === "domain-asc" + ? a.destination.localeCompare(b.destination) + : b.destination.localeCompare(a.destination); + return destCompare; + case "status-enabled": + return b.enabled ? 1 : -1; + case "status-disabled": + return a.enabled ? 1 : -1; + default: + return a.name.localeCompare(b.name); + } + }); + + setFilteredSiteResources(filteredSites); + // Reset to first page when search/sort changes setCurrentPage(1); - }, [resources, searchQuery, sortBy]); + }, [resources, siteResources, searchQuery, sortBy]); // Calculate pagination - const totalPages = Math.ceil(filteredResources.length / itemsPerPage); + const totalItems = filteredResources.length + filteredSiteResources.length; + const totalPages = Math.ceil(totalItems / itemsPerPage); const startIndex = (currentPage - 1) * itemsPerPage; const paginatedResources = filteredResources.slice( startIndex, startIndex + itemsPerPage ); + const remainingSlots = itemsPerPage - paginatedResources.length; + const paginatedSiteResources = remainingSlots > 0 + ? filteredSiteResources.slice( + Math.max(0, startIndex - filteredResources.length), + Math.max(0, startIndex - filteredResources.length) + remainingSlots + ) + : []; const handleOpenResource = (resource: Resource) => { // Open the resource in a new tab @@ -575,7 +635,7 @@ export default function MemberResourcesPortal({
    {/* Resources Content */} - {filteredResources.length === 0 ? ( + {filteredResources.length === 0 && filteredSiteResources.length === 0 ? ( /* Enhanced Empty State */ @@ -623,9 +683,20 @@ export default function MemberResourcesPortal({ ) : ( <> - {/* Resources Grid */} -
    - {paginatedResources.map((resource) => ( + {/* Public Resources Section */} + {paginatedResources.length > 0 && ( + <> +
    +

    + + Public Resources +

    +

    + Web applications and services accessible via browser +

    +
    +
    + {paginatedResources.map((resource) => (
    @@ -702,13 +773,167 @@ export default function MemberResourcesPortal({ ))}
    + + )} + + {/* Private Resources (Site Resources) Section */} + {paginatedSiteResources.length > 0 && ( + <> +
    +

    + + Private Resources +

    +

    + Internal network resources accessible via client +

    +
    +
    + {paginatedSiteResources.map((siteResource) => ( + +
    +
    +
    + + + + + {siteResource.name} + + + +

    + {siteResource.name} +

    +
    +
    +
    +
    + +
    + +
    +
    Resource Details
    +
    + Mode: + + {siteResource.mode} + +
    + {siteResource.protocol && ( +
    + Protocol: + + {siteResource.protocol} + +
    + )} + {siteResource.alias && ( +
    + Alias: + + {siteResource.alias} + +
    + )} + {siteResource.aliasAddress && ( +
    + Alias Address: + + {siteResource.aliasAddress} + +
    + )} +
    + Status: + + {siteResource.enabled ? 'Enabled' : 'Disabled'} + +
    +
    +
    +
    +
    + +
    + {siteResource.alias ? ( + <> + {/* Alias as primary */} +
    +
    + {siteResource.alias} +
    + +
    + {/* Destination as secondary */} +
    + {siteResource.destination} +
    + + ) : ( + /* Destination as primary when no alias */ +
    +
    + {siteResource.destination} +
    + +
    + )} +
    +
    + +
    +
    + + Requires Client Connection +
    +
    +
    + ))} +
    + + )} {/* Pagination Controls */} From f591cf8601e8e40913dbb169b0a9f3c203836511 Mon Sep 17 00:00:00 2001 From: Owen Date: Tue, 17 Feb 2026 20:06:58 -0800 Subject: [PATCH 75/81] Look to the right org to test is subscribed --- server/private/lib/billing/getOrgTierData.ts | 74 +++++++++++++------- 1 file changed, 49 insertions(+), 25 deletions(-) diff --git a/server/private/lib/billing/getOrgTierData.ts b/server/private/lib/billing/getOrgTierData.ts index 24d658c0..d87f2c38 100644 --- a/server/private/lib/billing/getOrgTierData.ts +++ b/server/private/lib/billing/getOrgTierData.ts @@ -12,7 +12,8 @@ */ import { build } from "@server/build"; -import { db, customers, subscriptions } from "@server/db"; +import { db, customers, subscriptions, orgs } from "@server/db"; +import logger from "@server/logger"; import { Tier } from "@server/types/Tiers"; import { eq, and, ne } from "drizzle-orm"; @@ -27,37 +28,60 @@ export async function getOrgTierData( } try { + const [org] = await db + .select() + .from(orgs) + .where(eq(orgs.orgId, orgId)) + .limit(1); + + if (!org) { + return { tier, active }; + } + + let orgIdToUse = org.orgId; + if (!org.isBillingOrg) { + if (!org.billingOrgId) { + logger.warn( + `Org ${orgId} is not a billing org and does not have a billingOrgId` + ); + return { tier, active }; + } + orgIdToUse = org.billingOrgId; + } + // Get customer for org const [customer] = await db .select() .from(customers) - .where(eq(customers.orgId, orgId)) + .where(eq(customers.orgId, orgIdToUse)) .limit(1); - if (customer) { - // Query for active subscriptions that are not license type - const [subscription] = await db - .select() - .from(subscriptions) - .where( - and( - eq(subscriptions.customerId, customer.customerId), - eq(subscriptions.status, "active"), - ne(subscriptions.type, "license") - ) - ) - .limit(1); + if (!customer) { + return { tier, active }; + } - if (subscription) { - // Validate that subscription.type is one of the expected tier values - if ( - subscription.type === "tier1" || - subscription.type === "tier2" || - subscription.type === "tier3" - ) { - tier = subscription.type; - active = true; - } + // Query for active subscriptions that are not license type + const [subscription] = await db + .select() + .from(subscriptions) + .where( + and( + eq(subscriptions.customerId, customer.customerId), + eq(subscriptions.status, "active"), + ne(subscriptions.type, "license") + ) + ) + .limit(1); + + if (subscription) { + // Validate that subscription.type is one of the expected tier values + if ( + subscription.type === "tier1" || + subscription.type === "tier2" || + subscription.type === "tier3" + ) { + tier = subscription.type; + active = true; } } } catch (error) { From d45ea127c255c8d8c9ce9359a37b17a08345f005 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Tue, 17 Feb 2026 20:07:04 -0800 Subject: [PATCH 76/81] use billing org id in get subscription status --- server/private/routers/billing/getOrgSubscriptions.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server/private/routers/billing/getOrgSubscriptions.ts b/server/private/routers/billing/getOrgSubscriptions.ts index d2ee8c5b..718c98f4 100644 --- a/server/private/routers/billing/getOrgSubscriptions.ts +++ b/server/private/routers/billing/getOrgSubscriptions.ts @@ -112,11 +112,13 @@ export async function getOrgSubscriptionsData( throw new Error(`Not found`); } + const billingOrgId = org[0].billingOrgId || org[0].orgId; + // Get customer for org const customer = await db .select() .from(customers) - .where(eq(customers.orgId, orgId)) + .where(eq(customers.orgId, billingOrgId)) .limit(1); const subscriptionsWithItems: Array<{ From 19fcc1f93b14d119d8ca5d95d27037ba4298f806 Mon Sep 17 00:00:00 2001 From: Owen Date: Tue, 17 Feb 2026 20:18:50 -0800 Subject: [PATCH 77/81] Set org limit --- server/lib/billing/limitSet.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/lib/billing/limitSet.ts b/server/lib/billing/limitSet.ts index d8c87c1c..ae9a18ff 100644 --- a/server/lib/billing/limitSet.ts +++ b/server/lib/billing/limitSet.ts @@ -64,7 +64,7 @@ export const tier3LimitSet: LimitSet = { description: "Business limit" }, [FeatureId.ORGINIZATIONS]: { - value: 20, + value: 5, description: "Business limit" }, }; From d4bff9d5cb1b988cfe5dbe3d8985e27a0494837e Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Tue, 17 Feb 2026 20:35:25 -0800 Subject: [PATCH 78/81] clean orgId and fix primary badge --- messages/en-US.json | 1 + server/routers/org/createOrg.ts | 11 ++++++++++- src/app/setup/page.tsx | 32 ++++++++++++++++++++++++++++++-- src/components/OrgSelector.tsx | 14 +++++++------- 4 files changed, 48 insertions(+), 10 deletions(-) diff --git a/messages/en-US.json b/messages/en-US.json index 35e88375..44d980c5 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -1031,6 +1031,7 @@ "pangolinSetup": "Setup - Pangolin", "orgNameRequired": "Organization name is required", "orgIdRequired": "Organization ID is required", + "orgIdMaxLength": "Organization ID must be at most 32 characters", "orgErrorCreate": "An error occurred while creating org", "pageNotFound": "Page Not Found", "pageNotFoundDescription": "Oops! The page you're looking for doesn't exist.", diff --git a/server/routers/org/createOrg.ts b/server/routers/org/createOrg.ts index e58f8207..59aa86d2 100644 --- a/server/routers/org/createOrg.ts +++ b/server/routers/org/createOrg.ts @@ -31,8 +31,17 @@ import { doCidrsOverlap } from "@server/lib/ip"; import { generateCA } from "@server/private/lib/sshCA"; import { encrypt } from "@server/lib/crypto"; +const validOrgIdRegex = /^[a-z0-9_]+(-[a-z0-9_]+)*$/; + const createOrgSchema = z.strictObject({ - orgId: z.string(), + orgId: z + .string() + .min(1, "Organization ID is required") + .max(32, "Organization ID must be at most 32 characters") + .refine((val) => validOrgIdRegex.test(val), { + message: + "Organization ID must contain only lowercase letters, numbers, underscores, and single hyphens (no leading, trailing, or consecutive hyphens)" + }), name: z.string().min(1).max(255), subnet: z // .union([z.cidrv4(), z.cidrv6()]) diff --git a/src/app/setup/page.tsx b/src/app/setup/page.tsx index dc505b67..c7e6de6a 100644 --- a/src/app/setup/page.tsx +++ b/src/app/setup/page.tsx @@ -50,7 +50,10 @@ export default function StepperForm() { const orgSchema = z.object({ orgName: z.string().min(1, { message: t("orgNameRequired") }), - orgId: z.string().min(1, { message: t("orgIdRequired") }), + orgId: z + .string() + .min(1, { message: t("orgIdRequired") }) + .max(32, { message: t("orgIdMaxLength") }), subnet: z.string().min(1, { message: t("subnetRequired") }), utilitySubnet: z.string().min(1, { message: t("subnetRequired") }) }); @@ -140,6 +143,16 @@ export default function StepperForm() { .replace(/^-+|-+$/g, ""); }; + const sanitizeOrgId = (value: string) => { + return value + .toLowerCase() + .replace(/\s+/g, "-") + .replace(/[^a-z0-9_-]/g, "") + .replace(/-+/g, "-") + .replace(/^-+|-+$/g, "") + .slice(0, 32); + }; + async function orgSubmit(values: z.infer) { if (orgIdTaken) { return; @@ -303,7 +316,22 @@ export default function StepperForm() { {t("orgId")} - + { + const value = sanitizeOrgId( + e.target.value + ); + field.onChange(value); + setOrgIdTaken(false); + if (value) { + debouncedCheckOrgIdAvailability( + value + ); + } + }} + /> diff --git a/src/components/OrgSelector.tsx b/src/components/OrgSelector.tsx index f5351362..cacaf553 100644 --- a/src/components/OrgSelector.tsx +++ b/src/components/OrgSelector.tsx @@ -154,22 +154,22 @@ export function OrgSelector({
    -
    - - {org.name} + + {org.name} + +
    + + {org.orgId} {org.isPrimaryOrg && ( {t("primary")} )}
    - - {org.orgId} -
    Date: Tue, 17 Feb 2026 20:39:37 -0800 Subject: [PATCH 79/81] Count everything when deleting the org --- server/lib/deleteOrg.ts | 75 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 73 insertions(+), 2 deletions(-) diff --git a/server/lib/deleteOrg.ts b/server/lib/deleteOrg.ts index 856759ab..c0656c2a 100644 --- a/server/lib/deleteOrg.ts +++ b/server/lib/deleteOrg.ts @@ -4,14 +4,18 @@ import { clientSitesAssociationsCache, db, domains, + exitNodeOrgs, + exitNodes, olms, orgDomains, orgs, + remoteExitNodes, resources, - sites + sites, + userOrgs } from "@server/db"; import { newts, newtSessions } from "@server/db"; -import { eq, and, inArray, sql } from "drizzle-orm"; +import { eq, and, inArray, sql, count, countDistinct } from "drizzle-orm"; import HttpCode from "@server/types/HttpCode"; import createHttpError from "http-errors"; import logger from "@server/logger"; @@ -62,6 +66,11 @@ export async function deleteOrgById( const deletedNewtIds: string[] = []; const olmsToTerminate: string[] = []; + let domainCount: number | null = null; + let siteCount: number | null = null; + let userCount: number | null = null; + let remoteExitNodeCount: number | null = null; + await db.transaction(async (trx) => { for (const site of orgSites) { if (site.pubKey) { @@ -141,8 +150,70 @@ export async function deleteOrgById( await usageService.add(orgId, FeatureId.ORGINIZATIONS, -1, trx); // here we are decreasing the org count BEFORE deleting the org because we need to still be able to get the org to get the billing org inside of here await trx.delete(orgs).where(eq(orgs.orgId, orgId)); + + if (org.billingOrgId) { + const billingOrgs = await trx + .select() + .from(orgs) + .where(eq(orgs.billingOrgId, org.billingOrgId)); + + if (billingOrgs.length > 0) { + const billingOrgIds = billingOrgs.map((org) => org.orgId); + + const [domainCountRes] = await trx + .select({ count: count() }) + .from(orgDomains) + .where(inArray(orgDomains.orgId, billingOrgIds)); + + domainCount = domainCountRes.count; + + const [siteCountRes] = await trx + .select({ count: count() }) + .from(sites) + .where(inArray(sites.orgId, billingOrgIds)); + + siteCount = siteCountRes.count; + + const [userCountRes] = await trx + .select({ count: countDistinct(userOrgs.userId) }) + .from(userOrgs) + .where(inArray(userOrgs.orgId, billingOrgIds)); + + userCount = userCountRes.count; + + const [remoteExitNodeCountRes] = await trx + .select({ count: countDistinct(exitNodeOrgs.exitNodeId) }) + .from(exitNodeOrgs) + .where(inArray(exitNodeOrgs.orgId, billingOrgIds)); + + remoteExitNodeCount = remoteExitNodeCountRes.count; + } + } }); + if (org.billingOrgId) { + usageService.updateCount( + org.billingOrgId, + FeatureId.DOMAINS, + domainCount ?? 0 + ); + usageService.updateCount( + org.billingOrgId, + FeatureId.SITES, + siteCount ?? 0 + ); + usageService.updateCount( + org.billingOrgId, + FeatureId.USERS, + userCount ?? 0 + ); + usageService.updateCount( + org.billingOrgId, + FeatureId.REMOTE_EXIT_NODES, + remoteExitNodeCount ?? 0 + ); + } + return { deletedNewtIds, olmsToTerminate }; } From 057f82a5614168e2d6b0614c96170b81cc427f58 Mon Sep 17 00:00:00 2001 From: Owen Date: Tue, 17 Feb 2026 20:46:02 -0800 Subject: [PATCH 80/81] Fix some cosmetics --- src/app/[orgId]/settings/(private)/billing/page.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/app/[orgId]/settings/(private)/billing/page.tsx b/src/app/[orgId]/settings/(private)/billing/page.tsx index 9d672902..bad8bda2 100644 --- a/src/app/[orgId]/settings/(private)/billing/page.tsx +++ b/src/app/[orgId]/settings/(private)/billing/page.tsx @@ -1067,6 +1067,17 @@ export default function BillingPage() { "Domains"}
    +
    + + + { + tierLimits[pendingTier.tier] + .organizations + }{" "} + {t("billingOrganizations") || + "Organizations"} + +
    From 2b0d6de986f2c161910a2af621a7048f3380206b Mon Sep 17 00:00:00 2001 From: Owen Date: Tue, 17 Feb 2026 21:00:48 -0800 Subject: [PATCH 81/81] Handle feature lifecycle for multiple orgs --- .../routers/billing/featureLifecycle.ts | 60 ++++++++++++++----- 1 file changed, 45 insertions(+), 15 deletions(-) diff --git a/server/private/routers/billing/featureLifecycle.ts b/server/private/routers/billing/featureLifecycle.ts index 35345444..3e4b8a4a 100644 --- a/server/private/routers/billing/featureLifecycle.ts +++ b/server/private/routers/billing/featureLifecycle.ts @@ -15,7 +15,18 @@ import { SubscriptionType } from "./hooks/getSubType"; import { TierFeature, tierMatrix } from "@server/lib/billing/tierMatrix"; import { Tier } from "@server/types/Tiers"; import logger from "@server/logger"; -import { db, idp, idpOrg, loginPage, loginPageBranding, loginPageBrandingOrg, loginPageOrg, orgs, resources, roles } from "@server/db"; +import { + db, + idp, + idpOrg, + loginPage, + loginPageBranding, + loginPageBrandingOrg, + loginPageOrg, + orgs, + resources, + roles +} from "@server/db"; import { eq } from "drizzle-orm"; /** @@ -59,10 +70,7 @@ async function capRetentionDays( } // Get current org settings - const [org] = await db - .select() - .from(orgs) - .where(eq(orgs.orgId, orgId)); + const [org] = await db.select().from(orgs).where(eq(orgs.orgId, orgId)); if (!org) { logger.warn(`Org ${orgId} not found when capping retention days`); @@ -110,18 +118,13 @@ async function capRetentionDays( // Apply updates if needed if (needsUpdate) { - await db - .update(orgs) - .set(updates) - .where(eq(orgs.orgId, orgId)); + await db.update(orgs).set(updates).where(eq(orgs.orgId, orgId)); logger.info( `Successfully capped retention days for org ${orgId} to max ${maxRetentionDays} days` ); } else { - logger.debug( - `No retention day capping needed for org ${orgId}` - ); + logger.debug(`No retention day capping needed for org ${orgId}`); } } @@ -134,6 +137,35 @@ export async function handleTierChange( `Handling tier change for org ${orgId}: ${previousTier || "none"} -> ${newTier || "free"}` ); + // Get all orgs that have this orgId as their billingOrgId + const associatedOrgs = await db + .select() + .from(orgs) + .where(eq(orgs.billingOrgId, orgId)); + + logger.info( + `Found ${associatedOrgs.length} org(s) associated with billing org ${orgId}` + ); + + // Loop over all associated orgs and apply tier changes + for (const org of associatedOrgs) { + await handleTierChangeForOrg(org.orgId, newTier, previousTier); + } + + logger.info( + `Completed tier change handling for all orgs associated with billing org ${orgId}` + ); +} + +async function handleTierChangeForOrg( + orgId: string, + newTier: SubscriptionType | null, + previousTier?: SubscriptionType | null +): Promise { + logger.info( + `Handling tier change for org ${orgId}: ${previousTier || "none"} -> ${newTier || "free"}` + ); + // License subscriptions are handled separately and don't use the tier matrix if (newTier === "license") { logger.debug( @@ -314,9 +346,7 @@ async function disableLoginPageDomain(orgId: string): Promise { ); if (existingLoginPage) { - await db - .delete(loginPageOrg) - .where(eq(loginPageOrg.orgId, orgId)); + await db.delete(loginPageOrg).where(eq(loginPageOrg.orgId, orgId)); await db .delete(loginPage)