From aa9872a45b1776f3f5bacfd6f125750a14ad9d14 Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Tue, 31 Dec 2024 10:30:14 +0530 Subject: [PATCH] chore: switch to react-router v7 --- .gitignore | 2 +- app/components/Attribute.tsx | 58 +- app/components/Button.tsx | 43 +- app/components/Card.tsx | 50 +- app/components/Code.tsx | 46 +- app/components/Dialog.tsx | 119 +- app/components/Error.tsx | 45 +- app/components/Footer.tsx | 42 +- app/components/Header.tsx | 178 +- app/components/Link.tsx | 15 +- app/components/Menu.tsx | 93 +- app/components/Notice.tsx | 24 +- app/components/NumberField.tsx | 39 +- app/components/Select.tsx | 40 +- app/components/Spinner.tsx | 17 +- app/components/StatusCircle.tsx | 20 +- app/components/Switch.tsx | 34 +- app/components/TabLink.tsx | 33 +- app/components/TableList.tsx | 27 +- app/components/TextField.tsx | 36 +- app/components/Toaster.tsx | 87 +- app/components/Tooltip.tsx | 38 +- app/entry.server.tsx | 55 +- app/integration/docker.ts | 127 +- app/integration/index.ts | 61 +- app/integration/integration.ts | 14 +- app/integration/kubernetes.ts | 191 +- app/integration/proc.ts | 74 +- app/layouts/dashboard.tsx | 48 +- app/root.tsx | 31 +- app/routes.ts | 7 +- app/routes/acls/components/cm.client.tsx | 182 +- app/routes/acls/components/error.tsx | 25 +- app/routes/acls/components/fallback.tsx | 8 +- app/routes/acls/components/unavailable.tsx | 38 +- app/routes/acls/editor.tsx | 302 +- app/routes/auth/login.tsx | 175 +- app/routes/auth/logout.ts | 14 +- app/routes/auth/oidc-callback.ts | 14 +- app/routes/dns/components/dns.tsx | 73 +- app/routes/dns/components/domains.tsx | 191 +- app/routes/dns/components/magic.tsx | 50 +- app/routes/dns/components/nameservers.tsx | 141 +- app/routes/dns/components/rename.tsx | 92 +- app/routes/dns/dialogs/dns.tsx | 116 +- app/routes/dns/dialogs/nameserver.tsx | 168 +- app/routes/dns/overview.tsx | 95 +- app/routes/machines/action.tsx | 215 +- app/routes/machines/components/machine.tsx | 158 +- app/routes/machines/components/menu.tsx | 115 +- app/routes/machines/dialogs/delete.tsx | 39 +- app/routes/machines/dialogs/expire.tsx | 38 +- app/routes/machines/dialogs/move.tsx | 71 +- app/routes/machines/dialogs/new.tsx | 106 +- app/routes/machines/dialogs/rename.tsx | 97 +- app/routes/machines/dialogs/routes.tsx | 213 +- app/routes/machines/dialogs/tags.tsx | 108 +- app/routes/machines/machine.tsx | 274 +- app/routes/machines/overview.tsx | 114 +- app/routes/settings/auth-keys.tsx | 198 +- app/routes/settings/components/key.tsx | 39 +- app/routes/settings/dialogs/expire.tsx | 57 +- app/routes/settings/dialogs/new.tsx | 113 +- app/routes/settings/overview.tsx | 44 +- app/routes/users/components/auth.tsx | 26 +- app/routes/users/components/oidc.tsx | 34 +- app/routes/users/dialogs/add.tsx | 64 +- app/routes/users/dialogs/remove.tsx | 49 +- app/routes/users/dialogs/rename.tsx | 49 +- app/routes/users/overview.tsx | 292 +- app/routes/util/healthz.ts | 27 +- app/routes/util/redirect.ts | 4 +- app/tailwind.css | 9 +- app/types/Key.ts | 2 +- app/types/Machine.ts | 43 +- app/types/PreAuthKey.ts | 18 +- app/types/Route.ts | 20 +- app/types/User.ts | 6 +- app/types/index.ts | 10 +- app/utils/cn.ts | 6 +- app/utils/config/headplane.ts | 224 +- app/utils/config/headscale.ts | 247 +- app/utils/headscale.ts | 102 +- app/utils/log.ts | 16 +- app/utils/oidc.ts | 148 +- app/utils/res.ts | 4 +- app/utils/sessions.ts | 20 +- app/utils/useLiveData.ts | 32 +- app/utils/ws.ts | 44 +- biome.json | 33 + package.json | 8 +- pnpm-lock.yaml | 3402 ++------------------ postcss.config.js | 10 +- react-router.config.ts | 5 + server/dev.mjs | 30 +- server/prod.mjs | 154 +- server/utils.mjs | 4 +- server/ws.mjs | 26 +- tailwind.config.ts | 18 +- tsconfig.json | 2 +- vite.config.ts | 56 +- 101 files changed, 3825 insertions(+), 6796 deletions(-) create mode 100644 biome.json create mode 100644 react-router.config.ts diff --git a/.gitignore b/.gitignore index 80ec311..b0426b7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ node_modules - +/.react-router /.cache /build .env diff --git a/app/components/Attribute.tsx b/app/components/Attribute.tsx index 1d8c6e6..256a16e 100644 --- a/app/components/Attribute.tsx +++ b/app/components/Attribute.tsx @@ -1,16 +1,15 @@ -import { CopyIcon } from '@primer/octicons-react' - -import { toast } from './Toaster' +import { CopyIcon } from '@primer/octicons-react'; +import { toast } from './Toaster'; interface Props { - name: string - value: string - isCopyable?: boolean - link?: string + name: string; + value: string; + isCopyable?: boolean; + link?: string; } export default function Attribute({ name, value, link, isCopyable }: Props) { - const canCopy = isCopyable ?? false + const canCopy = isCopyable ?? false; return (
@@ -18,31 +17,26 @@ export default function Attribute({ name, value, link, isCopyable }: Props) { {name} - ) : name} + ) : ( + name + )}
- {canCopy - ? ( - - ) - : ( -
- {value} -
- )} + {canCopy ? ( + + ) : ( +
{value}
+ )}
- ) + ); } diff --git a/app/components/Button.tsx b/app/components/Button.tsx index fbe2e72..dae1df0 100644 --- a/app/components/Button.tsx +++ b/app/components/Button.tsx @@ -1,37 +1,38 @@ -import { type Dispatch, type SetStateAction } from 'react' -import { Button as AriaButton } from 'react-aria-components' +import { Dispatch, SetStateAction } from 'react'; +import { Button as AriaButton } from 'react-aria-components'; +import { cn } from '~/utils/cn'; -import { cn } from '~/utils/cn' +type Props = Parameters[0] & { + readonly control?: [boolean, Dispatch>]; + readonly variant?: 'heavy' | 'light'; +}; -type ButtonProperties = Parameters[0] & { - readonly control?: [boolean, Dispatch>] - readonly variant?: 'heavy' | 'light' -} - -export default function Button(properties: ButtonProperties) { +export default function Button(props: Props) { return ( { - properties.control?.[1](true) - } - : properties.onPress} + onPress={ + props.control + ? () => { + props.control?.[1](true); + } + : props.onPress + } /> - ) + ); } diff --git a/app/components/Card.tsx b/app/components/Card.tsx index eea885e..e8f775f 100644 --- a/app/components/Card.tsx +++ b/app/components/Card.tsx @@ -1,53 +1,43 @@ -import { type HTMLProps } from 'react' -import { Heading as AriaHeading } from 'react-aria-components' +import { HTMLProps } from 'react'; +import { Heading as AriaHeading } from 'react-aria-components'; +import { cn } from '~/utils/cn'; -import { cn } from '~/utils/cn' - -function Title(properties: Parameters[0]) { +function Title(props: Parameters[0]) { return ( - ) + ); } -function Text(properties: React.HTMLProps) { +function Text(props: React.HTMLProps) { return ( -

- ) +

+ ); } -type Properties = HTMLProps & { +type Props = HTMLProps & { variant?: 'raised' | 'flat'; -} +}; -function Card(properties: Properties) { +function Card(props: Props) { return (

- {properties.children} + {props.children}
- ) + ); } -export default Object.assign(Card, { Title, Text }) +export default Object.assign(Card, { Title, Text }); diff --git a/app/components/Code.tsx b/app/components/Code.tsx index 2f7e80b..9bf08de 100644 --- a/app/components/Code.tsx +++ b/app/components/Code.tsx @@ -1,45 +1,47 @@ -import { useState, HTMLProps } from 'react' -import { CopyIcon, CheckIcon } from '@primer/octicons-react' - -import { cn } from '~/utils/cn' -import { toast } from '~/components/Toaster' +import { useState, HTMLProps } from 'react'; +import { CopyIcon, CheckIcon } from '@primer/octicons-react'; +import { cn } from '~/utils/cn'; +import { toast } from '~/components/Toaster'; interface Props extends HTMLProps { - isCopyable?: boolean + isCopyable?: boolean; } export default function Code(props: Props) { - const [isCopied, setIsCopied] = useState(false) + const [isCopied, setIsCopied] = useState(false); return ( <> - + {props.children} - {props.isCopyable && ( + {props.isCopyable && props.children ? ( - )} + ) : undefined} - ) + ); } diff --git a/app/components/Dialog.tsx b/app/components/Dialog.tsx index 9c43c0a..6e1d611 100644 --- a/app/components/Dialog.tsx +++ b/app/components/Dialog.tsx @@ -1,106 +1,99 @@ -/* eslint-disable unicorn/no-keyword-prefix */ -import { type Dispatch, type ReactNode, type SetStateAction } from 'react' +import { Dispatch, ReactNode, SetStateAction } from 'react'; import { Button as AriaButton, Dialog as AriaDialog, DialogTrigger, Heading as AriaHeading, Modal, - ModalOverlay -} from 'react-aria-components' + ModalOverlay, +} from 'react-aria-components'; +import { cn } from '~/utils/cn'; -import { cn } from '~/utils/cn' - -type ButtonProperties = Parameters[0] & { +type ButtonProps = Parameters[0] & { readonly control?: [boolean, Dispatch>]; -} +}; -function Button(properties: ButtonProperties) { +function Button(props: ButtonProps) { return ( { - properties.control?.[1](true) - } : undefined} + onPress={ + props.control + ? () => { + props.control?.[1](true); + } + : undefined + } /> - ) + ); } -type ActionProperties = Parameters[0] & { +type ActionProps = Parameters[0] & { readonly variant: 'cancel' | 'confirm'; -} +}; -function Action(properties: ActionProperties) { +function Action(props: ActionProps) { return ( - ) + ); } -function Title(properties: Parameters[0]) { +function Title(props: Parameters[0]) { return ( - ) + ); } -function Text(properties: React.HTMLProps) { +function Text(props: React.HTMLProps) { return ( -

- ) +

+ ); } -type PanelProperties = { - readonly children: (close: () => void) => ReactNode; - readonly control?: [boolean, Dispatch>]; - readonly className?: string; +interface PanelProps { + children: (close: () => void) => ReactNode; + control?: [boolean, Dispatch>]; + className?: string; } -function Panel({ children, control, className }: PanelProperties) { +function Panel({ children, control, className }: PanelProps) { return ( - ) + ); } -type DialogProperties = { - readonly children: ReactNode; - readonly control?: [boolean, Dispatch>]; +interface DialogProps { + children: ReactNode; + control?: [boolean, Dispatch>]; } -function Dialog({ children, control }: DialogProperties) { +function Dialog({ children, control }: DialogProps) { if (control) { - return children + return children; } - return ( - - {children} - - ) + return {children}; } -export default Object.assign(Dialog, { Button, Title, Text, Panel, Action }) +export default Object.assign(Dialog, { Button, Title, Text, Panel, Action }); diff --git a/app/components/Error.tsx b/app/components/Error.tsx index 79ae97f..e5f942b 100644 --- a/app/components/Error.tsx +++ b/app/components/Error.tsx @@ -1,19 +1,18 @@ -import { AlertIcon } from '@primer/octicons-react' -import { isRouteErrorResponse, useRouteError } from '@remix-run/react' +import { AlertIcon } from '@primer/octicons-react'; +import { isRouteErrorResponse, useRouteError } from 'react-router'; +import { cn } from '~/utils/cn'; +import Card from './Card'; +import Code from './Code'; -import { cn } from '~/utils/cn' - -import Card from './Card' -import Code from './Code' - -type Properties = { - readonly type?: 'full' | 'embedded'; +interface Props { + type?: 'full' | 'embedded'; } -export function ErrorPopup({ type = 'full' }: Properties) { - const error = useRouteError() - const routing = isRouteErrorResponse(error) - const message = (error instanceof Error ? error.message : 'An unexpected error occurred') +export function ErrorPopup({ type = 'full' }: Props) { + const error = useRouteError(); + const routing = isRouteErrorResponse(error); + const message = + error instanceof Error ? error.message : 'An unexpected error occurred'; return (

-
- +
+ {routing ? error.status : 'Error'} - +
- - {routing ? ( - error.statusText - ) : ( - - {message} - - )} + + {routing ? error.statusText : {message}}
- ) + ); } diff --git a/app/components/Footer.tsx b/app/components/Footer.tsx index 4a949b9..132e129 100644 --- a/app/components/Footer.tsx +++ b/app/components/Footer.tsx @@ -1,44 +1,36 @@ -import { cn } from '~/utils/cn' -import Link from '~/components/Link' +import { cn } from '~/utils/cn'; +import Link from '~/components/Link'; interface FooterProps { - url: string - debug: boolean + url: string; + debug: boolean; } export default function Footer({ url, debug, integration }: FooterProps) { return ( -
+

- Headplane is entirely free to use. - {' '} - If you find it useful, consider - {' '} + Headplane is entirely free to use. If you find it useful, consider{' '} donating - - {' '} - to support development. - {' '} + {' '} + to support development.{' '}

Version: {__VERSION__} {' | '} - Connecting to - {' '} - {url} - {' '} - {debug && '(Debug mode enabled)'} + Connecting to {url} {debug && '(Debug mode enabled)'}

- ) + ); } - diff --git a/app/components/Header.tsx b/app/components/Header.tsx index d6de368..58ea2c7 100644 --- a/app/components/Header.tsx +++ b/app/components/Header.tsx @@ -1,42 +1,51 @@ -import { GearIcon, GlobeIcon, LockIcon, PaperAirplaneIcon, PeopleIcon, PersonIcon, ServerIcon } from '@primer/octicons-react' -import { Form } from '@remix-run/react' +import { + GearIcon, + GlobeIcon, + LockIcon, + PaperAirplaneIcon, + PeopleIcon, + PersonIcon, + ServerIcon, +} from '@primer/octicons-react'; +import { Form } from 'react-router'; -import { cn } from '~/utils/cn' -import { HeadplaneContext } from '~/utils/config/headplane' -import { type SessionData } from '~/utils/sessions' +import { cn } from '~/utils/cn'; +import { HeadplaneContext } from '~/utils/config/headplane'; +import { SessionData } from '~/utils/sessions'; -import Menu from './Menu' -import TabLink from './TabLink' +import Menu from './Menu'; +import TabLink from './TabLink'; -interface Properties { - readonly data?: { - config: HeadplaneContext['config'] - user?: SessionData['user'] - } +interface Props { + data?: { + config: HeadplaneContext['config']; + user?: SessionData['user']; + }; } -interface LinkProperties { - readonly href: string - readonly text: string - readonly isMenu?: boolean +interface LinkProps { + href: string; + text: string; + isMenu?: boolean; } -function Link({ href, text, isMenu }: LinkProperties) { +function Link({ href, text, isMenu }: LinkProps) { return ( - ) + ); } -export default function Header({ data }: Properties) { +export default function Header({ data }: Props) { return (
@@ -48,69 +57,86 @@ export default function Header({ data }: Properties) { - {data?.user - ? ( - - + - - - - -

{data.user.name}

-

{data.user.email}

-
- - - - - - - - - - -
- -
-
-
-
- ) - : undefined} + > + + + + +

{data.user.name}

+

{data.user.email}

+
+ + + + + + + + + + +
+ +
+
+
+ + ) : undefined}
- ) + ); } diff --git a/app/components/Link.tsx b/app/components/Link.tsx index 0f78a95..fbd30fa 100644 --- a/app/components/Link.tsx +++ b/app/components/Link.tsx @@ -1,12 +1,11 @@ -import { LinkExternalIcon } from '@primer/octicons-react' - -import { cn } from '~/utils/cn' +import { LinkExternalIcon } from '@primer/octicons-react'; +import { cn } from '~/utils/cn'; interface Props { - to: string - name: string - children: string - className?: string + to: string; + name: string; + children: string; + className?: string; } export default function Link({ to, name: alt, children, className }: Props) { @@ -26,5 +25,5 @@ export default function Link({ to, name: alt, children, className }: Props) { {children} - ) + ); } diff --git a/app/components/Menu.tsx b/app/components/Menu.tsx index 6898320..6eb975d 100644 --- a/app/components/Menu.tsx +++ b/app/components/Menu.tsx @@ -1,98 +1,91 @@ -import { type Dispatch, type ReactNode, type SetStateAction } from 'react' +import { Dispatch, ReactNode, SetStateAction } from 'react'; import { Button as AriaButton, Menu as AriaMenu, MenuItem, MenuTrigger, - Popover -} from 'react-aria-components' + Popover, +} from 'react-aria-components'; +import { cn } from '~/utils/cn'; -import { cn } from '~/utils/cn' - -function Button(properties: Parameters[0]) { +function Button(props: Parameters[0]) { return ( - ) + ); } -function Items(properties: Parameters[0]) { +function Items(props: Parameters[0]) { return ( - - {properties.children} + {props.children} - ) + ); } -type ButtonProperties = Parameters[0] & { +type ButtonProps = Parameters[0] & { readonly control?: [boolean, Dispatch>]; -} +}; -function ItemButton(properties: ButtonProperties) { +function ItemButton(props: ButtonProps) { return ( - + { - properties.onPress?.(event) - properties.control?.[1](true) + onPress={(event) => { + props.onPress?.(event); + props.control?.[1](true); }} /> - ) + ); } -function Item(properties: Parameters[0]) { +function Item(props: Parameters[0]) { return ( - ) + ); } -function Menu({ children }: { readonly children: ReactNode }) { - return ( - - {children} - - ) +function Menu({ children }: { children: ReactNode }) { + return {children}; } -export default Object.assign(Menu, { Button, Item, ItemButton, Items }) +export default Object.assign(Menu, { Button, Item, ItemButton, Items }); diff --git a/app/components/Notice.tsx b/app/components/Notice.tsx index db58c25..8fdf5bb 100644 --- a/app/components/Notice.tsx +++ b/app/components/Notice.tsx @@ -1,23 +1,23 @@ -import { InfoIcon } from '@primer/octicons-react' -import type { ReactNode } from 'react' - -import { cn } from '~/utils/cn' +import { InfoIcon } from '@primer/octicons-react'; +import type { ReactNode } from 'react'; +import { cn } from '~/utils/cn'; interface Props { - className?: string - children: ReactNode + className?: string; + children: ReactNode; } export default function Notice({ children, className }: Props) { return ( -
{children}
- ) + ); } diff --git a/app/components/NumberField.tsx b/app/components/NumberField.tsx index c6cc5e1..dd20b50 100644 --- a/app/components/NumberField.tsx +++ b/app/components/NumberField.tsx @@ -1,18 +1,17 @@ -import { PlusIcon, DashIcon } from '@primer/octicons-react' -import { Dispatch, SetStateAction } from 'react' +import { PlusIcon, DashIcon } from '@primer/octicons-react'; +import { Dispatch, SetStateAction } from 'react'; import { Button, Group, Input, - NumberField as AriaNumberField -} from 'react-aria-components' - -import { cn } from '~/utils/cn' + NumberField as AriaNumberField, +} from 'react-aria-components'; +import { cn } from '~/utils/cn'; type NumberFieldProps = Parameters[0] & { label: string; state?: [number, Dispatch>]; -} +}; export default function NumberField(props: NumberFieldProps) { return ( @@ -21,20 +20,20 @@ export default function NumberField(props: NumberFieldProps) { aria-label={props.label} className="w-full" value={props.state?.[0]} - onChange={value => { - props.state?.[1](value) + onChange={(value) => { + props.state?.[1](value); }} > - - + + @@ -43,5 +42,5 @@ export default function NumberField(props: NumberFieldProps) { - ) + ); } diff --git a/app/components/Select.tsx b/app/components/Select.tsx index fd5f012..a2e83b7 100644 --- a/app/components/Select.tsx +++ b/app/components/Select.tsx @@ -1,5 +1,5 @@ -import { ChevronDownIcon } from '@primer/octicons-react' -import { Dispatch, ReactNode, SetStateAction } from 'react' +import { ChevronDownIcon } from '@primer/octicons-react'; +import { Dispatch, ReactNode, SetStateAction } from 'react'; import { Button, ListBox, @@ -7,15 +7,14 @@ import { Popover, Select as AriaSelect, SelectValue, -} from 'react-aria-components' - -import { cn } from '~/utils/cn' +} from 'react-aria-components'; +import { cn } from '~/utils/cn'; type SelectProps = Parameters[0] & { - readonly label: string - readonly state?: [string, Dispatch>] - readonly children: ReactNode -} + readonly label: string; + readonly state?: [string, Dispatch>]; + readonly children: ReactNode; +}; function Select(props: SelectProps) { return ( @@ -24,7 +23,7 @@ function Select(props: SelectProps) { aria-label={props.label} selectedKey={props.state?.[0]} onSelectionChange={(key) => { - props.state?.[1](key.toString()) + props.state?.[1](key.toString()); }} className={cn( 'block w-full rounded-lg my-1', @@ -34,10 +33,11 @@ function Select(props: SelectProps) { props.className, )} > - - ) + ); } const toasts = new ToastQueue({ - maxVisibleToasts: 5 -}) + maxVisibleToasts: 5, +}); export function toast(text: string) { - return toasts.add(text, { timeout: 5000 }) + return toasts.add(text, { timeout: 5000 }); } export function Toaster() { - const reference = useRef(null) - const state = useToastQueue(toasts) + const reference = useRef(null); + const state = useToastQueue(toasts); // @ts-expect-error: React 19 has weird types for Portal vs Node - const { regionProps } = useToastRegion({}, state, reference) + const { regionProps } = useToastRegion({}, state, reference); return ( { // @ts-expect-error: Portal doesn't match Node in React 19 yet - () => createPortal( - state.visibleToasts.length >= 0 ? ( -
- {state.visibleToasts.map(toast => ( - - ))} -
- ) : undefined, - document.body - )} + () => + createPortal( + state.visibleToasts.length >= 0 ? ( +
+ {state.visibleToasts.map((toast) => ( + + ))} +
+ ) : undefined, + document.body, + ) + }
- ) + ); } - diff --git a/app/components/Tooltip.tsx b/app/components/Tooltip.tsx index 9098e94..eaa4877 100644 --- a/app/components/Tooltip.tsx +++ b/app/components/Tooltip.tsx @@ -1,43 +1,37 @@ -import { ReactNode } from 'react' +import { ReactNode } from 'react'; import { Button as AriaButton, Tooltip as AriaTooltip, TooltipTrigger, -} from 'react-aria-components' - -import { cn } from '~/utils/cn' +} from 'react-aria-components'; +import { cn } from '~/utils/cn'; interface Props { - children: ReactNode - className?: string + children: ReactNode; + className?: string; } function Tooltip({ children }: Props) { - return ( - - {children} - - ) + return {children}; } function Button(props: Parameters[0]) { - return ( - - ) + return ; } function Body({ children, className }: Props) { return ( - {children} - ) + ); } -export default Object.assign(Tooltip, { Button, Body }) +export default Object.assign(Tooltip, { Button, Body }); diff --git a/app/entry.server.tsx b/app/entry.server.tsx index a882c08..efe5cc5 100644 --- a/app/entry.server.tsx +++ b/app/entry.server.tsx @@ -1,62 +1,59 @@ -import { PassThrough } from 'node:stream' +import { PassThrough } from 'node:stream'; -import type { AppLoadContext, EntryContext } from '@remix-run/node' -import { createReadableStreamFromReadable } from '@remix-run/node' -import { RemixServer } from '@remix-run/react' -import { isbot } from 'isbot' -import { renderToPipeableStream } from 'react-dom/server' +import type { AppLoadContext, EntryContext } from 'react-router'; +import { createReadableStreamFromReadable } from '@react-router/node'; +import { ServerRouter } from 'react-router'; +import { isbot } from 'isbot'; +import { renderToPipeableStream } from 'react-dom/server'; -import { loadContext } from './utils/config/headplane' +import { loadContext } from './utils/config/headplane'; -await loadContext() +await loadContext(); -export const streamTimeout = 5000 +export const streamTimeout = 5000; export default function handleRequest( request: Request, responseStatusCode: number, responseHeaders: Headers, - remixContext: EntryContext, + reactRouterContext: EntryContext, // eslint-disable-next-line @typescript-eslint/no-unused-vars _loadContext: AppLoadContext, ) { - const ua = request.headers.get('user-agent') - const isBot = ua ? isbot(ua) : false + const ua = request.headers.get('user-agent'); + const isBot = ua ? isbot(ua) : false; return new Promise((resolve, reject) => { - let shellRendered = false + let shellRendered = false; const { pipe, abort } = renderToPipeableStream( - , + , { [isBot ? 'onAllReady' : 'onShellReady']() { - shellRendered = true - const body = new PassThrough() - const stream = createReadableStreamFromReadable(body) - responseHeaders.set('Content-Type', 'text/html') + shellRendered = true; + const body = new PassThrough(); + const stream = createReadableStreamFromReadable(body); + responseHeaders.set('Content-Type', 'text/html'); resolve( new Response(stream, { headers: responseHeaders, status: responseStatusCode, }), - ) + ); - pipe(body) + pipe(body); }, onShellError(error: unknown) { - reject(error as Error) + reject(error as Error); }, onError(error: unknown) { - responseStatusCode = 500 + responseStatusCode = 500; if (shellRendered) { - console.error(error) + console.error(error); } }, }, - ) + ); - setTimeout(abort, streamTimeout + 1000) - }) + setTimeout(abort, streamTimeout + 1000); + }); } diff --git a/app/integration/docker.ts b/app/integration/docker.ts index b87e138..3b17b42 100644 --- a/app/integration/docker.ts +++ b/app/integration/docker.ts @@ -1,17 +1,17 @@ -import { access, constants } from 'node:fs/promises' -import { setTimeout } from 'node:timers/promises' +import { access, constants } from 'node:fs/promises'; +import { setTimeout } from 'node:timers/promises'; -import { Client } from 'undici' +import { Client } from 'undici'; -import { HeadscaleError, pull } from '~/utils/headscale' -import log from '~/utils/log' +import { HeadscaleError, pull } from '~/utils/headscale'; +import log from '~/utils/log'; -import { createIntegration } from './integration' +import { createIntegration } from './integration'; interface Context { - client: Client | undefined - container: string | undefined - maxAttempts: number + client: Client | undefined; + container: string | undefined; + maxAttempts: number; } export default createIntegration({ @@ -24,133 +24,126 @@ export default createIntegration({ isAvailable: async (context) => { // Check for the HEADSCALE_CONTAINER environment variable first // to avoid unnecessary fetching of the Docker socket - log.debug('INTG', 'Checking Docker integration availability') - context.container = process.env.HEADSCALE_CONTAINER - ?.trim() - .toLowerCase() + log.debug('INTG', 'Checking Docker integration availability'); + context.container = process.env.HEADSCALE_CONTAINER?.trim().toLowerCase(); if (!context.container || context.container.length === 0) { - log.error('INTG', 'Missing HEADSCALE_CONTAINER variable') - return false + log.error('INTG', 'Missing HEADSCALE_CONTAINER variable'); + return false; } - log.info('INTG', 'Using container: %s', context.container) - const path = process.env.DOCKER_SOCK ?? 'unix:///var/run/docker.sock' - let url: URL | undefined + log.info('INTG', 'Using container: %s', context.container); + const path = process.env.DOCKER_SOCK ?? 'unix:///var/run/docker.sock'; + let url: URL | undefined; try { - url = new URL(path) + url = new URL(path); } catch { - log.error('INTG', 'Invalid Docker socket path: %s', path) - return false + log.error('INTG', 'Invalid Docker socket path: %s', path); + return false; } if (url.protocol !== 'tcp:' && url.protocol !== 'unix:') { - log.error('INTG', 'Invalid Docker socket protocol: %s', - url.protocol, - ) - return false + log.error('INTG', 'Invalid Docker socket protocol: %s', url.protocol); + return false; } // The API is available as an HTTP endpoint and this // will simplify the fetching logic in undici if (url.protocol === 'tcp:') { // Apparently setting url.protocol doesn't work anymore? - const fetchU = url.href.replace(url.protocol, 'http:') + const fetchU = url.href.replace(url.protocol, 'http:'); try { - log.info('INTG', 'Checking API: %s', fetchU) - await fetch(new URL('/v1.30/version', fetchU).href) + log.info('INTG', 'Checking API: %s', fetchU); + await fetch(new URL('/v1.30/version', fetchU).href); } catch (error) { - log.debug('INTG', 'Failed to connect to Docker API', error) - log.error('INTG', 'Failed to connect to Docker API') - return false + log.debug('INTG', 'Failed to connect to Docker API', error); + log.error('INTG', 'Failed to connect to Docker API'); + return false; } - context.client = new Client(fetchU) + context.client = new Client(fetchU); } // Check if the socket is accessible if (url.protocol === 'unix:') { try { - log.info('INTG', 'Checking socket: %s', - url.pathname, - ) - await access(url.pathname, constants.R_OK) + log.info('INTG', 'Checking socket: %s', url.pathname); + await access(url.pathname, constants.R_OK); } catch (error) { - log.debug('INTG', 'Failed to access Docker socket: %s', error) - log.error('INTG', 'Failed to access Docker socket: %s', - path, - ) - return false + log.debug('INTG', 'Failed to access Docker socket: %s', error); + log.error('INTG', 'Failed to access Docker socket: %s', path); + return false; } context.client = new Client('http://localhost', { socketPath: url.pathname, - }) + }); } - return context.client !== undefined + return context.client !== undefined; }, onConfigChange: async (context) => { if (!context.client || !context.container) { - return + return; } - log.info('INTG', 'Restarting Headscale via Docker') + log.info('INTG', 'Restarting Headscale via Docker'); - let attempts = 0 + let attempts = 0; while (attempts <= context.maxAttempts) { log.debug( - 'INTG', 'Restarting container: %s (attempt %d)', + 'INTG', + 'Restarting container: %s (attempt %d)', context.container, attempts, - ) + ); const response = await context.client.request({ method: 'POST', path: `/v1.30/containers/${context.container}/restart`, - }) + }); if (response.statusCode !== 204) { if (attempts < context.maxAttempts) { - attempts++ - await setTimeout(1000) - continue + attempts++; + await setTimeout(1000); + continue; } - const stringCode = response.statusCode.toString() - const body = await response.body.text() - throw new Error(`API request failed: ${stringCode} ${body}`) + const stringCode = response.statusCode.toString(); + const body = await response.body.text(); + throw new Error(`API request failed: ${stringCode} ${body}`); } - break + break; } - attempts = 0 + attempts = 0; while (attempts <= context.maxAttempts) { try { - log.debug('INTG', 'Checking Headscale status (attempt %d)', attempts) - await pull('v1', '') - return + log.debug('INTG', 'Checking Headscale status (attempt %d)', attempts); + await pull('v1', ''); + return; } catch (error) { if (error instanceof HeadscaleError && error.status === 401) { - break + break; } if (error instanceof HeadscaleError && error.status === 404) { - break + break; } if (attempts < context.maxAttempts) { - attempts++ - await setTimeout(1000) - continue + attempts++; + await setTimeout(1000); + continue; } - throw new Error(`Missed restart deadline for ${context.container}`) + throw new Error(`Missed restart deadline for ${context.container}`); } } }, -}) +}); diff --git a/app/integration/index.ts b/app/integration/index.ts index 24b7dd0..2748c57 100644 --- a/app/integration/index.ts +++ b/app/integration/index.ts @@ -1,75 +1,68 @@ -import log from '~/utils/log' +import log from '~/utils/log'; -import dockerIntegration from './docker' -import { IntegrationFactory } from './integration' -import kubernetesIntegration from './kubernetes' -import procIntegration from './proc' +import dockerIntegration from './docker'; +import { IntegrationFactory } from './integration'; +import kubernetesIntegration from './kubernetes'; +import procIntegration from './proc'; -export * from './integration' +export * from './integration'; export async function loadIntegration() { - let integration = process.env.HEADSCALE_INTEGRATION - ?.trim() - .toLowerCase() + let integration = process.env.HEADSCALE_INTEGRATION?.trim().toLowerCase(); // Old HEADSCALE_CONTAINER variable upgrade path // This ensures that when people upgrade from older versions of Headplane // they don't explicitly need to define the new HEADSCALE_INTEGRATION // variable that is needed to configure docker if (!integration && process.env.HEADSCALE_CONTAINER) { - integration = 'docker' + integration = 'docker'; } if (!integration) { - log.info('INTG', 'No integration set with HEADSCALE_INTEGRATION') - return + log.info('INTG', 'No integration set with HEADSCALE_INTEGRATION'); + return; } - let integrationFactory: IntegrationFactory | undefined + let integrationFactory: IntegrationFactory | undefined; switch (integration.toLowerCase().trim()) { case 'docker': { - integrationFactory = dockerIntegration - break + integrationFactory = dockerIntegration; + break; } case 'proc': case 'native': case 'linux': { - integrationFactory = procIntegration - break + integrationFactory = procIntegration; + break; } case 'kubernetes': case 'k8s': { - integrationFactory = kubernetesIntegration - break + integrationFactory = kubernetesIntegration; + break; } default: { - log.error('INTG', 'Unknown integration: %s', integration) - throw new Error(`Unknown integration: ${integration}`) + log.error('INTG', 'Unknown integration: %s', integration); + throw new Error(`Unknown integration: ${integration}`); } } - log.info('INTG', 'Loading integration: %s', integration) + log.info('INTG', 'Loading integration: %s', integration); try { const res = await integrationFactory.isAvailable( integrationFactory.context, - ) + ); if (!res) { - log.error('INTG', 'Integration %s is not available', - integration, - ) - return + log.error('INTG', 'Integration %s is not available', integration); + return; } } catch (error) { - log.error('INTG', 'Failed to load integration %s: %s', - integration, - error, - ) - return + log.error('INTG', 'Failed to load integration %s: %s', integration, error); + return; } - log.info('INTG', 'Loaded integration: %s', integration) - return integrationFactory + log.info('INTG', 'Loaded integration: %s', integration); + return integrationFactory; } diff --git a/app/integration/integration.ts b/app/integration/integration.ts index 671fea2..990dc0b 100644 --- a/app/integration/integration.ts +++ b/app/integration/integration.ts @@ -1,13 +1,11 @@ // eslint-disable-next-line @typescript-eslint/no-explicit-any export interface IntegrationFactory { - name: string - context: T - isAvailable: (context: T) => Promise | boolean - onConfigChange?: (context: T) => Promise | void + name: string; + context: T; + isAvailable: (context: T) => Promise | boolean; + onConfigChange?: (context: T) => Promise | void; } -export function createIntegration( - options: IntegrationFactory, -) { - return options +export function createIntegration(options: IntegrationFactory) { + return options; } diff --git a/app/integration/kubernetes.ts b/app/integration/kubernetes.ts index f630e15..05502f2 100644 --- a/app/integration/kubernetes.ts +++ b/app/integration/kubernetes.ts @@ -1,16 +1,16 @@ -import { readdir, readFile } from 'node:fs/promises' -import { platform } from 'node:os' -import { join, resolve } from 'node:path' -import { kill } from 'node:process' +import { readdir, readFile } from 'node:fs/promises'; +import { platform } from 'node:os'; +import { join, resolve } from 'node:path'; +import { kill } from 'node:process'; -import { Config, CoreV1Api, KubeConfig } from '@kubernetes/client-node' +import { Config, CoreV1Api, KubeConfig } from '@kubernetes/client-node'; -import log from '~/utils/log' +import log from '~/utils/log'; -import { createIntegration } from './integration' +import { createIntegration } from './integration'; interface Context { - pid: number | undefined + pid: number | undefined; } export default createIntegration({ @@ -20,185 +20,192 @@ export default createIntegration({ }, isAvailable: async (context) => { if (platform() !== 'linux') { - log.error('INTG', 'Kubernetes is only available on Linux') - return false + log.error('INTG', 'Kubernetes is only available on Linux'); + return false; } - const svcRoot = Config.SERVICEACCOUNT_ROOT + const svcRoot = Config.SERVICEACCOUNT_ROOT; try { - log.debug('INTG', 'Checking Kubernetes service account at %s', svcRoot) - const files = await readdir(svcRoot) + log.debug('INTG', 'Checking Kubernetes service account at %s', svcRoot); + const files = await readdir(svcRoot); if (files.length === 0) { - log.error('INTG', 'Kubernetes service account not found') - return false + log.error('INTG', 'Kubernetes service account not found'); + return false; } - const mappedFiles = new Set(files.map(file => join(svcRoot, file))) + const mappedFiles = new Set(files.map((file) => join(svcRoot, file))); const expectedFiles = [ Config.SERVICEACCOUNT_CA_PATH, Config.SERVICEACCOUNT_TOKEN_PATH, Config.SERVICEACCOUNT_NAMESPACE_PATH, - ] + ]; - log.debug('INTG', 'Looking for %s', expectedFiles.join(', ')) - if (!expectedFiles.every(file => mappedFiles.has(file))) { - log.error('INTG', 'Malformed Kubernetes service account') - return false + log.debug('INTG', 'Looking for %s', expectedFiles.join(', ')); + if (!expectedFiles.every((file) => mappedFiles.has(file))) { + log.error('INTG', 'Malformed Kubernetes service account'); + return false; } } catch (error) { - log.error('INTG', 'Failed to access %s: %s', svcRoot, error) - return false + log.error('INTG', 'Failed to access %s: %s', svcRoot, error); + return false; } - log.debug('INTG', 'Reading Kubernetes service account at %s', svcRoot) + log.debug('INTG', 'Reading Kubernetes service account at %s', svcRoot); const namespace = await readFile( Config.SERVICEACCOUNT_NAMESPACE_PATH, 'utf8', - ) + ); // Some very ugly nesting but it's necessary if (process.env.HEADSCALE_INTEGRATION_UNSTRICT === 'true') { - log.warn('INTG', 'Skipping strict Pod status check') + log.warn('INTG', 'Skipping strict Pod status check'); } else { - const pod = process.env.POD_NAME + const pod = process.env.POD_NAME; if (!pod) { - log.error('INTG', 'Missing POD_NAME variable') - return false + log.error('INTG', 'Missing POD_NAME variable'); + return false; } if (pod.trim().length === 0) { - log.error('INTG', 'Pod name is empty') - return false + log.error('INTG', 'Pod name is empty'); + return false; } - log.debug('INTG', 'Checking Kubernetes pod %s in namespace %s', + log.debug( + 'INTG', + 'Checking Kubernetes pod %s in namespace %s', pod, namespace, - ) + ); try { - log.debug('INTG', 'Attempgin to get cluster KubeConfig') - const kc = new KubeConfig() - kc.loadFromCluster() + log.debug('INTG', 'Attempgin to get cluster KubeConfig'); + const kc = new KubeConfig(); + kc.loadFromCluster(); - const cluster = kc.getCurrentCluster() + const cluster = kc.getCurrentCluster(); if (!cluster) { - log.error('INTG', 'Malformed kubeconfig') - return false + log.error('INTG', 'Malformed kubeconfig'); + return false; } - log.info('INTG', 'Service account connected to %s (%s)', + log.info( + 'INTG', + 'Service account connected to %s (%s)', cluster.name, cluster.server, - ) + ); - const kCoreV1Api = kc.makeApiClient(CoreV1Api) + const kCoreV1Api = kc.makeApiClient(CoreV1Api); - log.info('INTG', 'Checking pod %s in namespace %s (%s)', + log.info( + 'INTG', + 'Checking pod %s in namespace %s (%s)', pod, namespace, kCoreV1Api.basePath, - ) + ); - log.debug('INTG', 'Reading pod info for %s', pod) + log.debug('INTG', 'Reading pod info for %s', pod); const { response, body } = await kCoreV1Api.readNamespacedPod( pod, namespace, - ) + ); if (response.statusCode !== 200) { - log.error('INTG', 'Failed to read pod info: http %d', - response.statusCode, - ) - return false - } - - log.debug('INTG', 'Got pod info: %o', body.spec) - const shared = body.spec?.shareProcessNamespace - if (shared === undefined) { log.error( 'INTG', - 'Pod does not have spec.shareProcessNamespace set', - ) - return false + 'Failed to read pod info: http %d', + response.statusCode, + ); + return false; + } + + log.debug('INTG', 'Got pod info: %o', body.spec); + const shared = body.spec?.shareProcessNamespace; + if (shared === undefined) { + log.error('INTG', 'Pod does not have spec.shareProcessNamespace set'); + return false; } if (!shared) { log.error( 'INTG', 'Pod has set but disabled spec.shareProcessNamespace', - ) - return false + ); + return false; } - log.info('INTG', 'Pod %s enabled shared processes', pod) + log.info('INTG', 'Pod %s enabled shared processes', pod); } catch (error) { - log.error('INTG', 'Failed to read pod info: %s', error) - return false + log.error('INTG', 'Failed to read pod info: %s', error); + return false; } } - log.debug('INTG', 'Looking for namespaced process in /proc') - const dir = resolve('/proc') + log.debug('INTG', 'Looking for namespaced process in /proc'); + const dir = resolve('/proc'); try { - const subdirs = await readdir(dir) + const subdirs = await readdir(dir); const promises = subdirs.map(async (dir) => { - const pid = Number.parseInt(dir, 10) + const pid = Number.parseInt(dir, 10); if (Number.isNaN(pid)) { - return + return; } - const path = join('/proc', dir, 'cmdline') + const path = join('/proc', dir, 'cmdline'); try { - log.debug('INTG', 'Reading %s', path) - const data = await readFile(path, 'utf8') + log.debug('INTG', 'Reading %s', path); + const data = await readFile(path, 'utf8'); if (data.includes('headscale')) { - return pid + return pid; } } catch (error) { - log.debug('INTG', 'Failed to read %s: %s', path, error) + log.debug('INTG', 'Failed to read %s: %s', path, error); } - }) + }); - const results = await Promise.allSettled(promises) - const pids = [] + const results = await Promise.allSettled(promises); + const pids = []; for (const result of results) { if (result.status === 'fulfilled' && result.value) { - pids.push(result.value) + pids.push(result.value); } } - log.debug('INTG', 'Found Headscale processes: %o', pids) + log.debug('INTG', 'Found Headscale processes: %o', pids); if (pids.length > 1) { - log.error('INTG', 'Found %d Headscale processes: %s', + log.error( + 'INTG', + 'Found %d Headscale processes: %s', pids.length, pids.join(', '), - ) - return false + ); + return false; } if (pids.length === 0) { - log.error('INTG', 'Could not find Headscale process') - return false + log.error('INTG', 'Could not find Headscale process'); + return false; } - context.pid = pids[0] - log.info('INTG', 'Found Headscale process with PID: %d', context.pid) - return true + context.pid = pids[0]; + log.info('INTG', 'Found Headscale process with PID: %d', context.pid); + return true; } catch { - log.error('INTG', 'Failed to read /proc') - return false + log.error('INTG', 'Failed to read /proc'); + return false; } }, onConfigChange: (context) => { if (!context.pid) { - return + return; } - log.info('INTG', 'Sending SIGTERM to Headscale') - kill(context.pid, 'SIGTERM') + log.info('INTG', 'Sending SIGTERM to Headscale'); + kill(context.pid, 'SIGTERM'); }, -}) +}); diff --git a/app/integration/proc.ts b/app/integration/proc.ts index fc56f84..12bc6e2 100644 --- a/app/integration/proc.ts +++ b/app/integration/proc.ts @@ -1,14 +1,14 @@ -import { readdir, readFile } from 'node:fs/promises' -import { platform } from 'node:os' -import { join, resolve } from 'node:path' -import { kill } from 'node:process' +import { readdir, readFile } from 'node:fs/promises'; +import { platform } from 'node:os'; +import { join, resolve } from 'node:path'; +import { kill } from 'node:process'; -import log from '~/utils/log' +import log from '~/utils/log'; -import { createIntegration } from './integration' +import { createIntegration } from './integration'; interface Context { - pid: number | undefined + pid: number | undefined; } export default createIntegration({ @@ -18,62 +18,64 @@ export default createIntegration({ }, isAvailable: async (context) => { if (platform() !== 'linux') { - log.error('INTG', '/proc is only available on Linux') - return false + log.error('INTG', '/proc is only available on Linux'); + return false; } - log.debug('INTG', 'Checking /proc for Headscale process') - const dir = resolve('/proc') + log.debug('INTG', 'Checking /proc for Headscale process'); + const dir = resolve('/proc'); try { - const subdirs = await readdir(dir) + const subdirs = await readdir(dir); const promises = subdirs.map(async (dir) => { - const pid = Number.parseInt(dir, 10) + const pid = Number.parseInt(dir, 10); if (Number.isNaN(pid)) { - return + return; } - const path = join('/proc', dir, 'cmdline') + const path = join('/proc', dir, 'cmdline'); try { - log.debug('INTG', 'Reading %s', path) - const data = await readFile(path, 'utf8') + log.debug('INTG', 'Reading %s', path); + const data = await readFile(path, 'utf8'); if (data.includes('headscale')) { - return pid + return pid; } } catch (error) { - log.error('INTG', 'Failed to read %s: %s', path, error) + log.error('INTG', 'Failed to read %s: %s', path, error); } - }) + }); - const results = await Promise.allSettled(promises) - const pids = [] + const results = await Promise.allSettled(promises); + const pids = []; for (const result of results) { if (result.status === 'fulfilled' && result.value) { - pids.push(result.value) + pids.push(result.value); } } - log.debug('INTG', 'Found Headscale processes: %o', pids) + log.debug('INTG', 'Found Headscale processes: %o', pids); if (pids.length > 1) { - log.error('INTG', 'Found %d Headscale processes: %s', + log.error( + 'INTG', + 'Found %d Headscale processes: %s', pids.length, pids.join(', '), - ) - return false + ); + return false; } if (pids.length === 0) { - log.error('INTG', 'Could not find Headscale process') - return false + log.error('INTG', 'Could not find Headscale process'); + return false; } - context.pid = pids[0] - log.info('INTG', 'Found Headscale process with PID: %d', context.pid) - return true + context.pid = pids[0]; + log.info('INTG', 'Found Headscale process with PID: %d', context.pid); + return true; } catch { - log.error('INTG', 'Failed to read /proc') - return false + log.error('INTG', 'Failed to read /proc'); + return false; } - } -}) + }, +}); diff --git a/app/layouts/dashboard.tsx b/app/layouts/dashboard.tsx index c855bb1..7540525 100644 --- a/app/layouts/dashboard.tsx +++ b/app/layouts/dashboard.tsx @@ -1,24 +1,24 @@ -import { LoaderFunctionArgs, redirect } from '@remix-run/node' -import { Outlet, useLoaderData, useNavigation } from '@remix-run/react' -import { ProgressBar } from 'react-aria-components' +import { LoaderFunctionArgs, redirect } from 'react-router'; +import { Outlet, useLoaderData, useNavigation } from 'react-router'; +import { ProgressBar } from 'react-aria-components'; -import { ErrorPopup } from '~/components/Error' -import Header from '~/components/Header' -import Footer from '~/components/Footer' -import Link from '~/components/Link' -import { cn } from '~/utils/cn' -import { loadContext } from '~/utils/config/headplane' -import { HeadscaleError, pull } from '~/utils/headscale' -import { destroySession, getSession } from '~/utils/sessions' +import { ErrorPopup } from '~/components/Error'; +import Header from '~/components/Header'; +import Footer from '~/components/Footer'; +import Link from '~/components/Link'; +import { cn } from '~/utils/cn'; +import { loadContext } from '~/utils/config/headplane'; +import { HeadscaleError, pull } from '~/utils/headscale'; +import { destroySession, getSession } from '~/utils/sessions'; export async function loader({ request }: LoaderFunctionArgs) { - const session = await getSession(request.headers.get('Cookie')) + const session = await getSession(request.headers.get('Cookie')); if (!session.has('hsApiKey')) { - return redirect('/login') + return redirect('/login'); } try { - await pull('v1/apikey', session.get('hsApiKey')!) + await pull('v1/apikey', session.get('hsApiKey')!); } catch (error) { if (error instanceof HeadscaleError) { // Safest to just redirect to login if we can't pull @@ -26,31 +26,29 @@ export async function loader({ request }: LoaderFunctionArgs) { headers: { 'Set-Cookie': await destroySession(session), }, - }) + }); } // Otherwise propagate to boundary - throw error + throw error; } - const context = await loadContext() + const context = await loadContext(); return { config: context.config, url: context.headscalePublicUrl ?? context.headscaleUrl, debug: context.debug, user: session.get('user'), - } + }; } export default function Layout() { - const data = useLoaderData() - const nav = useNavigation() + const data = useLoaderData(); + const nav = useNavigation(); return ( <> - +
- ) + ); } export function ErrorBoundary() { @@ -75,5 +73,5 @@ export function ErrorBoundary() {
- ) + ); } diff --git a/app/root.tsx b/app/root.tsx index a21e4b7..97eb2c8 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -1,24 +1,21 @@ -import type { LinksFunction, MetaFunction } from '@remix-run/node' -import { - Links, - Meta, - Outlet, - Scripts, - ScrollRestoration, -} from '@remix-run/react' +import type { LinksFunction, MetaFunction } from 'react-router'; +import { Links, Meta, Outlet, Scripts, ScrollRestoration } from 'react-router'; -import { ErrorPopup } from '~/components/Error' -import { Toaster } from '~/components/Toaster' -import stylesheet from '~/tailwind.css?url' +import { ErrorPopup } from '~/components/Error'; +import { Toaster } from '~/components/Toaster'; +import stylesheet from '~/tailwind.css?url'; export const meta: MetaFunction = () => [ { title: 'Headplane' }, - { name: 'description', content: 'A frontend for the headscale coordination server' }, -] + { + name: 'description', + content: 'A frontend for the headscale coordination server', + }, +]; export const links: LinksFunction = () => [ { rel: 'stylesheet', href: stylesheet }, -] +]; export function Layout({ children }: { readonly children: React.ReactNode }) { return ( @@ -36,13 +33,13 @@ export function Layout({ children }: { readonly children: React.ReactNode }) { - ) + ); } export function ErrorBoundary() { - return + return ; } export default function App() { - return + return ; } diff --git a/app/routes.ts b/app/routes.ts index 74d9e09..47c9cd3 100644 --- a/app/routes.ts +++ b/app/routes.ts @@ -1,5 +1,5 @@ //import { flatRoutes } from '@remix-run/fs-routes' -import { index, layout, prefix, route } from '@remix-run/route-config' +import { index, layout, prefix, route } from '@react-router/dev/routes'; export default [ // Utility Routes @@ -26,6 +26,5 @@ export default [ index('routes/settings/overview.tsx'), route('/auth-keys', 'routes/settings/auth-keys.tsx'), ]), - ]) -] - + ]), +]; diff --git a/app/routes/acls/components/cm.client.tsx b/app/routes/acls/components/cm.client.tsx index 4b89dc6..b47e34f 100644 --- a/app/routes/acls/components/cm.client.tsx +++ b/app/routes/acls/components/cm.client.tsx @@ -1,122 +1,136 @@ -import React, { useEffect } from 'react' -import Merge from 'react-codemirror-merge' -import CodeMirror from '@uiw/react-codemirror' -import * as shopify from '@shopify/lang-jsonc' -import { ClientOnly } from 'remix-utils/client-only' -import { ErrorBoundary } from 'react-error-boundary' -import { githubDark, githubLight } from '@uiw/codemirror-theme-github' -import { useState } from 'react' -import { cn } from '~/utils/cn' +import React, { useEffect } from 'react'; +import Merge from 'react-codemirror-merge'; +import CodeMirror from '@uiw/react-codemirror'; +import * as shopify from '@shopify/lang-jsonc'; +import { ClientOnly } from 'remix-utils/client-only'; +import { ErrorBoundary } from 'react-error-boundary'; +import { githubDark, githubLight } from '@uiw/codemirror-theme-github'; +import { useState } from 'react'; +import { cn } from '~/utils/cn'; -import Fallback from './fallback' +import Fallback from './fallback'; interface EditorProps { - isDisabled?: boolean - value: string - onChange: (value: string) => void + isDisabled?: boolean; + value: string; + onChange: (value: string) => void; } export function Editor(props: EditorProps) { - const [light, setLight] = useState(false) + const [light, setLight] = useState(false); useEffect(() => { - const theme = window.matchMedia('(prefers-color-scheme: light)') - setLight(theme.matches) + const theme = window.matchMedia('(prefers-color-scheme: light)'); + setLight(theme.matches); theme.addEventListener('change', (theme) => { - setLight(theme.matches) - }) - }) + setLight(theme.matches); + }); + }); return ( -
+
- - Failed to load the editor. -

- }> + + Failed to load the editor. +

+ } + > }> - {() => ( - props.onChange(value)} - /> - )} + {() => ( + props.onChange(value)} + /> + )}
- ) + ); } interface DifferProps { - left: string - right: string + left: string; + right: string; } export function Differ(props: DifferProps) { - const [light, setLight] = useState(false) + const [light, setLight] = useState(false); useEffect(() => { - const theme = window.matchMedia('(prefers-color-scheme: light)') - setLight(theme.matches) + const theme = window.matchMedia('(prefers-color-scheme: light)'); + setLight(theme.matches); theme.addEventListener('change', (theme) => { - setLight(theme.matches) - }) - }) + setLight(theme.matches); + }); + }); return ( -
+
{props.left === props.right ? ( -

+

No changes

) : ( - - Failed to load the editor. -

- }> - }> - {() => ( - - - - - )} + Failed to load the editor. +

+ } + > + }> + {() => ( + + + + + )}
)}
- ) + ); } diff --git a/app/routes/acls/components/error.tsx b/app/routes/acls/components/error.tsx index 853a948..07cabc1 100644 --- a/app/routes/acls/components/error.tsx +++ b/app/routes/acls/components/error.tsx @@ -1,30 +1,25 @@ -import { cn } from '~/utils/cn' -import { AlertIcon } from '@primer/octicons-react' +import { cn } from '~/utils/cn'; +import { AlertIcon } from '@primer/octicons-react'; -import Card from '~/components/Card' -import Code from '~/components/Code' +import Card from '~/components/Card'; +import Code from '~/components/Code'; interface Props { - message: string + message: string; } export function ErrorView({ message }: Props) { return (
- - Error - - + Error +
- Could not apply changes to your ACL policy - due to the following error: + Could not apply changes to your ACL policy due to the following error:
- - {message} - + {message}
- ) + ); } diff --git a/app/routes/acls/components/fallback.tsx b/app/routes/acls/components/fallback.tsx index bee3826..0441ebf 100644 --- a/app/routes/acls/components/fallback.tsx +++ b/app/routes/acls/components/fallback.tsx @@ -1,8 +1,8 @@ -import Spinner from '~/components/Spinner' -import { cn } from '~/utils/cn' +import Spinner from '~/components/Spinner'; +import { cn } from '~/utils/cn'; interface Props { - readonly acl: string + readonly acl: string; } export default function Fallback({ acl }: Props) { @@ -20,5 +20,5 @@ export default function Fallback({ acl }: Props) { value={acl} />
- ) + ); } diff --git a/app/routes/acls/components/unavailable.tsx b/app/routes/acls/components/unavailable.tsx index be605a7..58e20ec 100644 --- a/app/routes/acls/components/unavailable.tsx +++ b/app/routes/acls/components/unavailable.tsx @@ -1,43 +1,39 @@ -import { cn } from '~/utils/cn' -import { AlertIcon } from '@primer/octicons-react' +import { cn } from '~/utils/cn'; +import { AlertIcon } from '@primer/octicons-react'; -import Code from '~/components/Code' -import Card from '~/components/Card' +import Code from '~/components/Code'; +import Card from '~/components/Card'; interface Props { - mode: 'file' | 'database' + mode: 'file' | 'database'; } export function Unavailable({ mode }: Props) { return (
- - ACL Policy Unavailable - - + ACL Policy Unavailable +
- Unable to load a valid ACL policy configuration. - This is most likely due to a misconfiguration in your - Headscale configuration file. + Unable to load a valid ACL policy configuration. This is most likely due + to a misconfiguration in your Headscale configuration file. {mode !== 'file' ? (

- According to your configuration, the ACL policy mode - is set to file but the ACL file is not - available. Ensure that the policy.path is - set to a valid path in your Headscale configuration. + According to your configuration, the ACL policy mode is set to{' '} + file but the ACL file is not available. Ensure that the{' '} + policy.path is set to a valid path in your Headscale + configuration.

) : (

- In order to fully utilize the ACL management features of - Headplane, please set policy.mode to either - {' '}file or database in your - Headscale configuration. + In order to fully utilize the ACL management features of Headplane, + please set policy.mode to either file or{' '} + database in your Headscale configuration.

)}
- ) + ); } diff --git a/app/routes/acls/editor.tsx b/app/routes/acls/editor.tsx index 11fca02..325aa82 100644 --- a/app/routes/acls/editor.tsx +++ b/app/routes/acls/editor.tsx @@ -1,32 +1,37 @@ -import { BeakerIcon, EyeIcon, IssueDraftIcon, PencilIcon } from '@primer/octicons-react' -import { ActionFunctionArgs, LoaderFunctionArgs } from '@remix-run/node' -import { useLoaderData, useRevalidator } from '@remix-run/react' -import { useDebounceFetcher } from 'remix-utils/use-debounce-fetcher' -import { useEffect, useState, useMemo } from 'react' -import { Tab, TabList, TabPanel, Tabs } from 'react-aria-components' -import { setTimeout } from 'node:timers/promises' +import { + BeakerIcon, + EyeIcon, + IssueDraftIcon, + PencilIcon, +} from '@primer/octicons-react'; +import { ActionFunctionArgs, LoaderFunctionArgs } from 'react-router'; +import { useLoaderData, useRevalidator } from 'react-router'; +import { useDebounceFetcher } from 'remix-utils/use-debounce-fetcher'; +import { useEffect, useState, useMemo } from 'react'; +import { Tab, TabList, TabPanel, Tabs } from 'react-aria-components'; +import { setTimeout } from 'node:timers/promises'; -import Button from '~/components/Button' -import Code from '~/components/Code' -import Link from '~/components/Link' -import Notice from '~/components/Notice' -import Spinner from '~/components/Spinner' -import { toast } from '~/components/Toaster' -import { cn } from '~/utils/cn' -import { loadContext } from '~/utils/config/headplane' -import { loadConfig } from '~/utils/config/headscale' -import { HeadscaleError, pull, put } from '~/utils/headscale' -import { getSession } from '~/utils/sessions' -import { send } from '~/utils/res' -import log from '~/utils/log' +import Button from '~/components/Button'; +import Code from '~/components/Code'; +import Link from '~/components/Link'; +import Notice from '~/components/Notice'; +import Spinner from '~/components/Spinner'; +import { toast } from '~/components/Toaster'; +import { cn } from '~/utils/cn'; +import { loadContext } from '~/utils/config/headplane'; +import { loadConfig } from '~/utils/config/headscale'; +import { HeadscaleError, pull, put } from '~/utils/headscale'; +import { getSession } from '~/utils/sessions'; +import { send } from '~/utils/res'; +import log from '~/utils/log'; -import { Editor, Differ } from './components/cm.client' -import { Unavailable } from './components/unavailable' -import { ErrorView } from './components/error' +import { Editor, Differ } from './components/cm.client'; +import { Unavailable } from './components/unavailable'; +import { ErrorView } from './components/error'; export async function loader({ request }: LoaderFunctionArgs) { - const session = await getSession(request.headers.get('Cookie')) - + const session = await getSession(request.headers.get('Cookie')); + // The way policy is handled in 0.23 of Headscale and later is verbose. // The 2 ACL policy modes are either the database one or file one // @@ -51,11 +56,11 @@ export async function loader({ request }: LoaderFunctionArgs) { // We can do damage control by checking for write access and if we are not // able to PUT an ACL policy on the v1/policy route, we can already know // that the policy is at the very-least readonly or not available. - const context = await loadContext() - let modeGuess = 'database' // Assume database mode + const context = await loadContext(); + let modeGuess = 'database'; // Assume database mode if (context.config.read) { - const config = await loadConfig() - modeGuess = config.policy?.mode ?? 'database' + const config = await loadConfig(); + modeGuess = config.policy?.mode ?? 'database'; } // Attempt to load the policy, for both the frontend and for checking @@ -64,23 +69,19 @@ export async function loader({ request }: LoaderFunctionArgs) { const { policy } = await pull<{ policy: string }>( 'v1/policy', session.get('hsApiKey')!, - ) + ); - let write = false // On file mode we already know it's readonly + let write = false; // On file mode we already know it's readonly if (modeGuess === 'database' && policy.length > 0) { try { await put('v1/policy', session.get('hsApiKey')!, { policy: policy, - }) + }); - write = true + write = true; } catch (error) { - write = false - log.debug( - 'APIC', - 'Failed to write to ACL policy with error %s', - error - ) + write = false; + log.debug('APIC', 'Failed to write to ACL policy with error %s', error); } } @@ -88,8 +89,8 @@ export async function loader({ request }: LoaderFunctionArgs) { read: true, write, mode: modeGuess, - policy - } + policy, + }; } catch { // If we are explicit on file mode then this is the end of the road if (modeGuess === 'file') { @@ -97,8 +98,8 @@ export async function loader({ request }: LoaderFunctionArgs) { read: false, write: false, mode: modeGuess, - policy: null - } + policy: null, + }; } // Assume that we have write access otherwise? @@ -108,124 +109,119 @@ export async function loader({ request }: LoaderFunctionArgs) { read: true, write: true, mode: modeGuess, - policy: null - } + policy: null, + }; } } export async function action({ request }: ActionFunctionArgs) { - const session = await getSession(request.headers.get('Cookie')) + const session = await getSession(request.headers.get('Cookie')); if (!session.has('hsApiKey')) { - return send({ success: false, error: null }, 401) + return send({ success: false, error: null }, 401); } try { - const { acl } = await request.json() as { acl: string } + const { acl } = (await request.json()) as { acl: string }; const { policy } = await put<{ policy: string }>( 'v1/policy', session.get('hsApiKey')!, { policy: acl, - } - ) + }, + ); - return { success: true, policy, error: null } + return { success: true, policy, error: null }; } catch (error) { - log.debug('APIC', 'Failed to update ACL policy with error %s', error) + log.debug('APIC', 'Failed to update ACL policy with error %s', error); // @ts-ignore: Shut UP we know it's a string most of the time - const text = JSON.parse(error.message) - return send({ success: false, error: text.message }, { - status: error instanceof HeadscaleError ? error.status : 500, - }) + const text = JSON.parse(error.message); + return send( + { success: false, error: text.message }, + { + status: error instanceof HeadscaleError ? error.status : 500, + }, + ); } - return { success: true, error: null } + return { success: true, error: null }; } export default function Page() { - const data = useLoaderData() - const fetcher = useDebounceFetcher() - const revalidator = useRevalidator() + const data = useLoaderData(); + const fetcher = useDebounceFetcher(); + const revalidator = useRevalidator(); - const [acl, setAcl] = useState(data.policy ?? '') - const [toasted, setToasted] = useState(false) + const [acl, setAcl] = useState(data.policy ?? ''); + const [toasted, setToasted] = useState(false); useEffect(() => { if (!fetcher.data || toasted) { - return + return; } // @ts-ignore: useDebounceFetcher is not typed correctly if (fetcher.data.success) { - toast('Updated tailnet ACL policy') + toast('Updated tailnet ACL policy'); } else { - toast('Failed to update tailnet ACL policy') + toast('Failed to update tailnet ACL policy'); } - setToasted(true) + setToasted(true); if (revalidator.state === 'idle') { - revalidator.revalidate() + revalidator.revalidate(); } - }, [fetcher.data, toasted, data.policy]) + }, [fetcher.data, toasted, data.policy]); // The state for if the save and discard buttons should be disabled // is pretty complicated to calculate and varies on different states. const disabled = useMemo(() => { if (!data.read || !data.write) { - return true + return true; } // First check our fetcher states if (fetcher.state === 'loading') { - return true + return true; } if (revalidator.state === 'loading') { - return true + return true; } // If we have a failed fetcher state allow the user to try again // @ts-ignore: useDebounceFetcher is not typed correctly if (fetcher.data?.success === false) { - return false + return false; } - return data.policy === acl - }, [data, revalidator.state, fetcher.state, fetcher.data, data.policy, acl]) + return data.policy === acl; + }, [data, revalidator.state, fetcher.state, fetcher.data, data.policy, acl]); return (
- {data.read && !data.write - ? ( -
- - The ACL policy is read-only. You can view the current policy - but you cannot make changes to it. -
- To resolve this, you need to set the ACL policy mode to - database in your Headscale configuration. -
-
- ) : undefined} - -

- Access Control List (ACL) -

- + {data.read && !data.write ? ( +
+ + The ACL policy is read-only. You can view the current policy but you + cannot make changes to it. +
+ To resolve this, you need to set the ACL policy mode to database in + your Headscale configuration. +
+
+ ) : undefined} +

Access Control List (ACL)

- The ACL file is used to define the access control rules for your network. - You can find more information about the ACL file in the - {' '} + The ACL file is used to define the access control rules for your + network. You can find more information about the ACL file in the{' '} Tailscale ACL guide - - {' '} - and the - {' '} + {' '} + and the{' '} .

- { // @ts-ignore: useDebounceFetcher is not typed correctly - fetcher.data?.success === false - ? ( + fetcher.data?.success === false ? ( // @ts-ignore: useDebounceFetcher is not typed correctly - ) : undefined} - + ) : undefined + } {data.read ? ( <> - cn( - 'px-4 py-2 rounded-tl-lg', - 'focus:outline-none flex items-center gap-2', - 'border-x border-gray-200 dark:border-gray-700', - isSelected ? 'text-gray-900 dark:text-gray-100' : '', - )} + className={({ isSelected }) => + cn( + 'px-4 py-2 rounded-tl-lg', + 'focus:outline-none flex items-center gap-2', + 'border-x border-gray-200 dark:border-gray-700', + isSelected ? 'text-gray-900 dark:text-gray-100' : '', + ) + } >

Edit file

cn( - 'px-4 py-2', - 'focus:outline-none flex items-center gap-2', - 'border-x border-gray-200 dark:border-gray-700', - isSelected ? 'text-gray-900 dark:text-gray-100' : '', - )} + className={({ isSelected }) => + cn( + 'px-4 py-2', + 'focus:outline-none flex items-center gap-2', + 'border-x border-gray-200 dark:border-gray-700', + isSelected ? 'text-gray-900 dark:text-gray-100' : '', + ) + } >

Preview changes

cn( - 'px-4 py-2 rounded-tr-lg', - 'focus:outline-none flex items-center gap-2', - 'border-x border-gray-200 dark:border-gray-700', - isSelected ? 'text-gray-900 dark:text-gray-100' : '', - )} + className={({ isSelected }) => + cn( + 'px-4 py-2 rounded-tr-lg', + 'focus:outline-none flex items-center gap-2', + 'border-x border-gray-200 dark:border-gray-700', + isSelected ? 'text-gray-900 dark:text-gray-100' : '', + ) + } >

Preview rules

- + - +

- The Preview rules is very much still a work in progress. - It is a bit complicated to implement right now but hopefully it will be available soon. + The Preview rules is very much still a work in progress. It is + a bit complicated to implement right now but hopefully it will + be available soon.

@@ -323,32 +318,35 @@ export default function Page() { className="mr-2" isDisabled={disabled} onPress={() => { - setToasted(false) - fetcher.submit({ - acl, - }, { - method: 'PATCH', - encType: 'application/json', - }) + setToasted(false); + fetcher.submit( + { + acl, + }, + { + method: 'PATCH', + encType: 'application/json', + }, + ); }} > - {fetcher.state === 'idle' - ? undefined - : ( - - )} + {fetcher.state === 'idle' ? undefined : ( + + )} Save - ) : } + ) : ( + + )}
- ) + ); } diff --git a/app/routes/auth/login.tsx b/app/routes/auth/login.tsx index 6eebd7f..42e0096 100644 --- a/app/routes/auth/login.tsx +++ b/app/routes/auth/login.tsx @@ -1,75 +1,75 @@ -import { ActionFunctionArgs, LoaderFunctionArgs, redirect } from '@remix-run/node' -import { Form, useActionData, useLoaderData } from '@remix-run/react' -import { useMemo } from 'react' +import { ActionFunctionArgs, LoaderFunctionArgs, redirect } from 'react-router'; +import { Form, useActionData, useLoaderData } from 'react-router'; +import { useMemo } from 'react'; -import Button from '~/components/Button' -import Card from '~/components/Card' -import Code from '~/components/Code' -import TextField from '~/components/TextField' -import { Key } from '~/types' -import { loadContext } from '~/utils/config/headplane' -import { pull } from '~/utils/headscale' -import { startOidc } from '~/utils/oidc' -import { commitSession, getSession } from '~/utils/sessions' +import Button from '~/components/Button'; +import Card from '~/components/Card'; +import Code from '~/components/Code'; +import TextField from '~/components/TextField'; +import { Key } from '~/types'; +import { loadContext } from '~/utils/config/headplane'; +import { pull } from '~/utils/headscale'; +import { startOidc } from '~/utils/oidc'; +import { commitSession, getSession } from '~/utils/sessions'; export async function loader({ request }: LoaderFunctionArgs) { - const session = await getSession(request.headers.get('Cookie')) + const session = await getSession(request.headers.get('Cookie')); if (session.has('hsApiKey')) { return redirect('/machines', { headers: { 'Set-Cookie': await commitSession(session), }, - }) + }); } - const context = await loadContext() + const context = await loadContext(); // Only set if OIDC is properly enabled anyways if (context.oidc?.disableKeyLogin) { - return startOidc(context.oidc, request) + return startOidc(context.oidc, request); } return { oidc: context.oidc?.issuer, apiKey: !context.oidc?.disableKeyLogin, - } + }; } export async function action({ request }: ActionFunctionArgs) { - const formData = await request.formData() - const oidcStart = formData.get('oidc-start') + const formData = await request.formData(); + const oidcStart = formData.get('oidc-start'); if (oidcStart) { - const context = await loadContext() + const context = await loadContext(); if (!context.oidc) { - throw new Error('An invalid OIDC configuration was provided') + throw new Error('An invalid OIDC configuration was provided'); } // We know it exists here because this action only happens on OIDC - return startOidc(context.oidc, request) + return startOidc(context.oidc, request); } - const apiKey = String(formData.get('api-key')) - const session = await getSession(request.headers.get('Cookie')) + const apiKey = String(formData.get('api-key')); + const session = await getSession(request.headers.get('Cookie')); // Test the API key try { - const apiKeys = await pull<{ apiKeys: Key[] }>('v1/apikey', apiKey) - const key = apiKeys.apiKeys.find(k => apiKey.startsWith(k.prefix)) + const apiKeys = await pull<{ apiKeys: Key[] }>('v1/apikey', apiKey); + const key = apiKeys.apiKeys.find((k) => apiKey.startsWith(k.prefix)); if (!key) { - throw new Error('Invalid API key') + throw new Error('Invalid API key'); } - const expiry = new Date(key.expiration) - const expiresIn = expiry.getTime() - Date.now() - const expiresDays = Math.round(expiresIn / 1000 / 60 / 60 / 24) + const expiry = new Date(key.expiration); + const expiresIn = expiry.getTime() - Date.now(); + const expiresDays = Math.round(expiresIn / 1000 / 60 / 60 / 24); - session.set('hsApiKey', apiKey) + session.set('hsApiKey', apiKey); session.set('user', { name: key.prefix, email: `${expiresDays.toString()} days`, - }) + }); return redirect('/machines', { headers: { @@ -77,85 +77,62 @@ export async function action({ request }: ActionFunctionArgs) { maxAge: expiresIn, }), }, - }) + }); } catch { return { error: 'Invalid API key', - } + }; } } export default function Page() { - const data = useLoaderData() - const actionData = useActionData() - const showOr = useMemo(() => data.oidc && data.apiKey, [data]) + const data = useLoaderData(); + const actionData = useActionData(); + const showOr = useMemo(() => data.oidc && data.apiKey, [data]); return (
- - Welcome to Headplane - - {data.apiKey - ? ( -
- - Enter an API key to authenticate with Headplane. You can generate - one by running - {' '} - - headscale apikeys create - - {' '} - in your terminal. - + Welcome to Headplane + {data.apiKey ? ( + + + Enter an API key to authenticate with Headplane. You can generate + one by running headscale apikeys create in your + terminal. + - {actionData?.error - ? ( -

{actionData.error}

- ) - : undefined} - - - - ) - : undefined} - {showOr - ? ( -
-
- or -
-
- ) - : undefined} - {data.oidc - ? ( -
- - -
- ) - : undefined} + {actionData?.error ? ( +

{actionData.error}

+ ) : undefined} + + + + ) : undefined} + {showOr ? ( +
+
+ or +
+
+ ) : undefined} + {data.oidc ? ( +
+ + +
+ ) : undefined}
- ) + ); } diff --git a/app/routes/auth/logout.ts b/app/routes/auth/logout.ts index c5ddab2..ef27523 100644 --- a/app/routes/auth/logout.ts +++ b/app/routes/auth/logout.ts @@ -1,15 +1,15 @@ -import { ActionFunctionArgs, redirect } from '@remix-run/node' -import { destroySession, getSession } from '~/utils/sessions' +import { ActionFunctionArgs, redirect } from 'react-router'; +import { destroySession, getSession } from '~/utils/sessions'; export async function loader() { - return redirect('/machines') + return redirect('/machines'); } export async function action({ request }: ActionFunctionArgs) { - const session = await getSession(request.headers.get('Cookie')) + const session = await getSession(request.headers.get('Cookie')); return redirect('/login', { headers: { - 'Set-Cookie': await destroySession(session) - } - }) + 'Set-Cookie': await destroySession(session), + }, + }); } diff --git a/app/routes/auth/oidc-callback.ts b/app/routes/auth/oidc-callback.ts index e2a197b..9da7288 100644 --- a/app/routes/auth/oidc-callback.ts +++ b/app/routes/auth/oidc-callback.ts @@ -1,17 +1,17 @@ -import { LoaderFunctionArgs, data } from '@remix-run/node' -import { loadContext } from '~/utils/config/headplane' -import { finishOidc } from '~/utils/oidc' +import { LoaderFunctionArgs, data } from 'react-router'; +import { loadContext } from '~/utils/config/headplane'; +import { finishOidc } from '~/utils/oidc'; export async function loader({ request }: LoaderFunctionArgs) { try { - const context = await loadContext() + const context = await loadContext(); if (!context.oidc) { - throw new Error('An invalid OIDC configuration was provided') + throw new Error('An invalid OIDC configuration was provided'); } - return finishOidc(context.oidc, request) + return finishOidc(context.oidc, request); } catch (error) { // Gracefully present OIDC errors - return data({ error }, { status: 500 }) + return data({ error }, { status: 500 }); } } diff --git a/app/routes/dns/components/dns.tsx b/app/routes/dns/components/dns.tsx index 65309d8..98015eb 100644 --- a/app/routes/dns/components/dns.tsx +++ b/app/routes/dns/components/dns.tsx @@ -1,32 +1,27 @@ -import { useSubmit } from '@remix-run/react' -import { Button } from 'react-aria-components' +import { useSubmit } from 'react-router'; +import { Button } from 'react-aria-components'; -import Code from '~/components/Code' -import Link from '~/components/Link' -import TableList from '~/components/TableList' -import { cn } from '~/utils/cn' +import Code from '~/components/Code'; +import Link from '~/components/Link'; +import TableList from '~/components/TableList'; +import { cn } from '~/utils/cn'; -import AddDNS from '../dialogs/dns' +import AddDNS from '../dialogs/dns'; interface Props { - records: { name: string, type: 'A', value: string }[] - isDisabled: boolean + records: { name: string; type: 'A'; value: string }[]; + isDisabled: boolean; } export default function DNS({ records, isDisabled }: Props) { - const submit = useSubmit() + const submit = useSubmit(); return (

DNS Records

- Headscale supports adding custom DNS records to your Tailnet. - As of now, only - {' '} - A - {' '} - records are supported. - {' '} + Headscale supports adding custom DNS records to your Tailnet. As of now, + only A records are supported.{' '}

- {records.length === 0 - ? ( - -

- No DNS records found -

-
- ) - : records.map((record, index) => ( + {records.length === 0 ? ( + +

No DNS records found

+
+ ) : ( + records.map((record, index) => (
@@ -62,27 +54,28 @@ export default function DNS({ records, isDisabled }: Props) { )} isDisabled={isDisabled} onPress={() => { - submit({ - 'dns.extra_records': records - .filter((_, i) => i !== index), - }, { - method: 'PATCH', - encType: 'application/json', - }) + submit( + { + 'dns.extra_records': records.filter( + (_, i) => i !== index, + ), + }, + { + method: 'PATCH', + encType: 'application/json', + }, + ); }} > Remove - ))} + )) + )} - {isDisabled - ? undefined - : ( - - )} + {isDisabled ? undefined : }
- ) + ); } diff --git a/app/routes/dns/components/domains.tsx b/app/routes/dns/components/domains.tsx index 6b3493b..64ddfdd 100644 --- a/app/routes/dns/components/domains.tsx +++ b/app/routes/dns/components/domains.tsx @@ -1,88 +1,88 @@ /* eslint-disable unicorn/no-keyword-prefix */ -import { - closestCorners, - DndContext, - DragOverlay -} from '@dnd-kit/core' +import { closestCorners, DndContext, DragOverlay } from '@dnd-kit/core'; import { restrictToParentElement, - restrictToVerticalAxis -} from '@dnd-kit/modifiers' + restrictToVerticalAxis, +} from '@dnd-kit/modifiers'; import { arrayMove, SortableContext, useSortable, - verticalListSortingStrategy -} from '@dnd-kit/sortable' -import { CSS } from '@dnd-kit/utilities' -import { LockIcon, ThreeBarsIcon } from '@primer/octicons-react' -import { FetcherWithComponents, useFetcher } from '@remix-run/react' -import { useEffect, useState } from 'react' -import { Button, Input } from 'react-aria-components' + verticalListSortingStrategy, +} from '@dnd-kit/sortable'; +import { CSS } from '@dnd-kit/utilities'; +import { LockIcon, ThreeBarsIcon } from '@primer/octicons-react'; +import { FetcherWithComponents, useFetcher } from 'react-router'; +import { useEffect, useState } from 'react'; +import { Button, Input } from 'react-aria-components'; -import Spinner from '~/components/Spinner' -import TableList from '~/components/TableList' -import { cn } from '~/utils/cn' +import Spinner from '~/components/Spinner'; +import TableList from '~/components/TableList'; +import { cn } from '~/utils/cn'; type Properties = { readonly baseDomain?: string; readonly searchDomains: string[]; // eslint-disable-next-line react/boolean-prop-naming readonly disabled?: boolean; -} +}; -export default function Domains({ baseDomain, searchDomains, disabled }: Properties) { +export default function Domains({ + baseDomain, + searchDomains, + disabled, +}: Properties) { // eslint-disable-next-line unicorn/no-null, @typescript-eslint/ban-types - const [activeId, setActiveId] = useState(null) - const [localDomains, setLocalDomains] = useState(searchDomains) - const [newDomain, setNewDomain] = useState('') - const fetcher = useFetcher() + const [activeId, setActiveId] = useState(null); + const [localDomains, setLocalDomains] = useState(searchDomains); + const [newDomain, setNewDomain] = useState(''); + const fetcher = useFetcher(); useEffect(() => { - setLocalDomains(searchDomains) - }, [searchDomains]) + setLocalDomains(searchDomains); + }, [searchDomains]); return ( -
-

Search Domains

-

- Set custom DNS search domains for your Tailnet. - When using Magic DNS, your tailnet domain is used as the first search domain. +

+

Search Domains

+

+ Set custom DNS search domains for your Tailnet. When using Magic DNS, + your tailnet domain is used as the first search domain.

{ - setActiveId(event.active.id) + onDragStart={(event) => { + setActiveId(event.active.id); }} - onDragEnd={event => { + onDragEnd={(event) => { // eslint-disable-next-line unicorn/no-null - setActiveId(null) - const { active, over } = event + setActiveId(null); + const { active, over } = event; if (!over) { - return + return; } - const activeItem = localDomains[active.id as number - 1] - const overItem = localDomains[over.id as number - 1] + const activeItem = localDomains[(active.id as number) - 1]; + const overItem = localDomains[(over.id as number) - 1]; if (!activeItem || !overItem) { - return + return; } - const oldIndex = localDomains.indexOf(activeItem) - const newIndex = localDomains.indexOf(overItem) + const oldIndex = localDomains.indexOf(activeItem); + const newIndex = localDomains.indexOf(overItem); if (oldIndex !== newIndex) { - setLocalDomains(arrayMove(localDomains, oldIndex, newIndex)) + setLocalDomains(arrayMove(localDomains, oldIndex, newIndex)); } }} > {baseDomain ? ( - -

{baseDomain}

- + +

{baseDomain}

+
) : undefined} ))} - {activeId ? : undefined} + {activeId ? ( + + ) : undefined} {disabled ? undefined : ( - + { - setNewDomain(event.target.value) + onChange={(event) => { + setNewDomain(event.target.value); }} /> {fetcher.state === 'idle' ? ( @@ -128,32 +130,35 @@ export default function Domains({ baseDomain, searchDomains, disabled }: Propert 'text-sm font-semibold', 'text-blue-600 dark:text-blue-400', 'hover:text-blue-700 dark:hover:text-blue-300', - newDomain.length === 0 && 'opacity-50 cursor-not-allowed' + newDomain.length === 0 && 'opacity-50 cursor-not-allowed', )} isDisabled={newDomain.length === 0} onPress={() => { - fetcher.submit({ - // eslint-disable-next-line @typescript-eslint/naming-convention - 'dns.search_domains': [...localDomains, newDomain] - }, { - method: 'PATCH', - encType: 'application/json' - }) + fetcher.submit( + { + // eslint-disable-next-line @typescript-eslint/naming-convention + 'dns.search_domains': [...localDomains, newDomain], + }, + { + method: 'PATCH', + encType: 'application/json', + }, + ); - setNewDomain('') + setNewDomain(''); }} > Add ) : ( - + )} )}
- ) + ); } type DomainProperties = { @@ -164,17 +169,24 @@ type DomainProperties = { // eslint-disable-next-line react/boolean-prop-naming readonly disabled?: boolean; readonly fetcher: FetcherWithComponents; -} +}; -function Domain({ domain, id, localDomains, isDrag, disabled, fetcher }: DomainProperties) { +function Domain({ + domain, + id, + localDomains, + isDrag, + disabled, + fetcher, +}: DomainProperties) { const { attributes, listeners, setNodeRef, transform, transition, - isDragging - } = useSortable({ id }) + isDragging, + } = useSortable({ id }); // TODO: Figure out why TableList.Item breaks dndkit return ( @@ -184,17 +196,19 @@ function Domain({ domain, id, localDomains, isDrag, disabled, fetcher }: DomainP 'flex items-center justify-between px-3 py-2', 'border-b border-gray-200 last:border-b-0 dark:border-zinc-800', isDragging ? 'text-gray-400' : '', - isDrag ? 'outline outline-1 outline-gray-500 bg-gray-200 dark:bg-zinc-800' : '' + isDrag + ? 'outline outline-1 outline-gray-500 bg-gray-200 dark:bg-zinc-800' + : '', )} style={{ transform: CSS.Transform.toString(transform), - transition + transition, }} > -

+

{disabled ? undefined : ( @@ -207,21 +221,26 @@ function Domain({ domain, id, localDomains, isDrag, disabled, fetcher }: DomainP 'text-sm', 'text-red-600 dark:text-red-400', 'hover:text-red-700 dark:hover:text-red-300', - disabled && 'opacity-50 cursor-not-allowed' + disabled && 'opacity-50 cursor-not-allowed', )} isDisabled={disabled} onPress={() => { - fetcher.submit({ - 'dns.search_domains': localDomains.filter((_, index) => index !== id - 1) - }, { - method: 'PATCH', - encType: 'application/json' - }) + fetcher.submit( + { + 'dns.search_domains': localDomains.filter( + (_, index) => index !== id - 1, + ), + }, + { + method: 'PATCH', + encType: 'application/json', + }, + ); }} > Remove )}

- ) + ); } diff --git a/app/routes/dns/components/magic.tsx b/app/routes/dns/components/magic.tsx index 2002abe..0822195 100644 --- a/app/routes/dns/components/magic.tsx +++ b/app/routes/dns/components/magic.tsx @@ -1,53 +1,51 @@ -import { useFetcher } from '@remix-run/react' +import { useFetcher } from 'react-router'; -import Dialog from '~/components/Dialog' -import Spinner from '~/components/Spinner' +import Dialog from '~/components/Dialog'; +import Spinner from '~/components/Spinner'; type Properties = { readonly isEnabled: boolean; readonly disabled?: boolean; -} +}; export default function Modal({ isEnabled, disabled }: Properties) { - const fetcher = useFetcher() + const fetcher = useFetcher(); return ( - {fetcher.state === 'idle' ? undefined : ( - - )} + {fetcher.state === 'idle' ? undefined : } {isEnabled ? 'Disable' : 'Enable'} Magic DNS - {close => ( + {(close) => ( <> {isEnabled ? 'Disable' : 'Enable'} Magic DNS - Devices will no longer be accessible via your tailnet domain. - The search domain will also be disabled. + Devices will no longer be accessible via your tailnet domain. The + search domain will also be disabled. -
- +
+ Cancel { - fetcher.submit({ - // eslint-disable-next-line @typescript-eslint/naming-convention - 'dns.magic_dns': !isEnabled - }, { - method: 'PATCH', - encType: 'application/json' - }) + fetcher.submit( + { + // eslint-disable-next-line @typescript-eslint/naming-convention + 'dns.magic_dns': !isEnabled, + }, + { + method: 'PATCH', + encType: 'application/json', + }, + ); - close() + close(); }} > {isEnabled ? 'Disable' : 'Enable'} Magic DNS @@ -57,5 +55,5 @@ export default function Modal({ isEnabled, disabled }: Properties) { )}
- ) + ); } diff --git a/app/routes/dns/components/nameservers.tsx b/app/routes/dns/components/nameservers.tsx index 837766a..9d057b3 100644 --- a/app/routes/dns/components/nameservers.tsx +++ b/app/routes/dns/components/nameservers.tsx @@ -1,17 +1,17 @@ -import { useSubmit } from '@remix-run/react' -import { useState } from 'react' -import { Button } from 'react-aria-components' +import { useSubmit } from 'react-router'; +import { useState } from 'react'; +import { Button } from 'react-aria-components'; -import Link from '~/components/Link' -import Switch from '~/components/Switch' -import TableList from '~/components/TableList' -import { cn } from '~/utils/cn' +import Link from '~/components/Link'; +import Switch from '~/components/Switch'; +import TableList from '~/components/TableList'; +import { cn } from '~/utils/cn'; -import AddNameserver from '../dialogs/nameserver' +import AddNameserver from '../dialogs/nameserver'; interface Props { - nameservers: Record - isDisabled: boolean + nameservers: Record; + isDisabled: boolean; } export default function Nameservers({ nameservers, isDisabled }: Props) { @@ -19,9 +19,8 @@ export default function Nameservers({ nameservers, isDisabled }: Props) {

Nameservers

- Set the nameservers used by devices on the Tailnet - to resolve DNS queries. - {' '} + Set the nameservers used by devices on the Tailnet to resolve DNS + queries.{' '}

- {Object.keys(nameservers).map(key => ( + {Object.keys(nameservers).map((key) => ( ))} - {isDisabled - ? undefined - : ( - - )} + {isDisabled ? undefined : }
- ) + ); } interface ListProps { - isGlobal: boolean - isDisabled: boolean - nameservers: string[] - name: string + isGlobal: boolean; + isDisabled: boolean; + nameservers: string[]; + name: string; } -function NameserverList({ isGlobal, isDisabled, nameservers, name }: ListProps) { - const submit = useSubmit() - const list = isGlobal ? nameservers['global'] : nameservers[name] +function NameserverList({ + isGlobal, + isDisabled, + nameservers, + name, +}: ListProps) { + const submit = useSubmit(); + const list = isGlobal ? nameservers['global'] : nameservers[name]; if (list.length === 0) { - return null + return null; } return ( @@ -72,45 +72,54 @@ function NameserverList({ isGlobal, isDisabled, nameservers, name }: ListProps)
- {list.length > 0 ? list.map((ns, index) => ( - // eslint-disable-next-line react/no-array-index-key - -

{ns}

- -
- )) : undefined} + }} + > + Remove + + + )) + : undefined}
- ) + ); } diff --git a/app/routes/dns/components/rename.tsx b/app/routes/dns/components/rename.tsx index 1f6d4f7..7ae391a 100644 --- a/app/routes/dns/components/rename.tsx +++ b/app/routes/dns/components/rename.tsx @@ -1,34 +1,28 @@ -import { useFetcher } from '@remix-run/react' -import { useState } from 'react' -import { Input } from 'react-aria-components' +import { useFetcher } from 'react-router'; +import { useState } from 'react'; +import { Input } from 'react-aria-components'; -import Code from '~/components/Code' -import Dialog from '~/components/Dialog' -import Spinner from '~/components/Spinner' -import TextField from '~/components/TextField' -import { cn } from '~/utils/cn' +import Code from '~/components/Code'; +import Dialog from '~/components/Dialog'; +import Spinner from '~/components/Spinner'; +import TextField from '~/components/TextField'; +import { cn } from '~/utils/cn'; type Properties = { readonly name: string; readonly disabled?: boolean; -} +}; export default function Modal({ name, disabled }: Properties) { - const [newName, setNewName] = useState(name) - const fetcher = useFetcher() + const [newName, setNewName] = useState(name); + const fetcher = useFetcher(); return ( -
-

Tailnet Name

-

- This is the base domain name of your Tailnet. - Devices are accessible at - {' '} - - [device].{name} - - {' '} - when Magic DNS is enabled. +

+

Tailnet Name

+

+ This is the base domain name of your Tailnet. Devices are accessible at{' '} + [device].{name} when Magic DNS is enabled.

{ - event.target.select() + onFocus={(event) => { + event.target.select(); }} /> {fetcher.state === 'idle' ? undefined : ( - + )} Rename Tailnet - {close => ( + {(close) => ( <> - - Rename Tailnet - + Rename Tailnet - Keep in mind that changing this can lead to all sorts of unexpected behavior and may break existing devices in your tailnet. + Keep in mind that changing this can lead to all sorts of + unexpected behavior and may break existing devices in your + tailnet. -
- +
+ Cancel { - fetcher.submit({ - 'dns.base_domain': newName - }, { - method: 'PATCH', - encType: 'application/json' - }) + fetcher.submit( + { + 'dns.base_domain': newName, + }, + { + method: 'PATCH', + encType: 'application/json', + }, + ); - close() + close(); }} > Rename @@ -94,5 +88,5 @@ export default function Modal({ name, disabled }: Properties) {
- ) + ); } diff --git a/app/routes/dns/dialogs/dns.tsx b/app/routes/dns/dialogs/dns.tsx index f3668a3..e2836de 100644 --- a/app/routes/dns/dialogs/dns.tsx +++ b/app/routes/dns/dialogs/dns.tsx @@ -1,66 +1,65 @@ -import { Form, useSubmit } from '@remix-run/react' -import { useMemo, useState } from 'react' +import { Form, useSubmit } from 'react-router'; +import { useMemo, useState } from 'react'; -import Code from '~/components/Code' -import Dialog from '~/components/Dialog' -import TextField from '~/components/TextField' -import { cn } from '~/utils/cn' +import Code from '~/components/Code'; +import Dialog from '~/components/Dialog'; +import TextField from '~/components/TextField'; +import { cn } from '~/utils/cn'; interface Props { - records: { name: string, type: 'A', value: string }[] + records: { name: string; type: 'A'; value: string }[]; } export default function AddDNS({ records }: Props) { - const submit = useSubmit() - const [name, setName] = useState('') - const [ip, setIp] = useState('') + const submit = useSubmit(); + const [name, setName] = useState(''); + const [ip, setIp] = useState(''); const isDuplicate = useMemo(() => { - if (name.length === 0 || ip.length === 0) return false - const lookup = records.find(record => record.name === name) - if (!lookup) return false + if (name.length === 0 || ip.length === 0) return false; + const lookup = records.find((record) => record.name === name); + if (!lookup) return false; - return lookup.value === ip - }, [records, name, ip]) + return lookup.value === ip; + }, [records, name, ip]); return ( - - Add DNS record - + Add DNS record - {close => ( + {(close) => ( <> - - Add DNS record - + Add DNS record Enter the domain and IP address for the new DNS record.
{ - event.preventDefault() - if (!name || !ip) return + event.preventDefault(); + if (!name || !ip) return; - setName('') - setIp('') + setName(''); + setIp(''); - submit({ - 'dns.extra_records': [ - ...records, - { - name, - type: 'A', - value: ip, - }, - ], - }, { - method: 'PATCH', - encType: 'application/json', - }) + submit( + { + 'dns.extra_records': [ + ...records, + { + name, + type: 'A', + value: ip, + }, + ], + }, + { + method: 'PATCH', + encType: 'application/json', + }, + ); - close() + close(); }} > - {isDuplicate - ? ( -

- A record with the domain name - {' '} - {name} - {' '} - and IP address - {' '} - {ip} - {' '} - already exists. -

- ) - : undefined} + {isDuplicate ? ( +

+ A record with the domain name {name} and IP + address {ip} already exists. +

+ ) : undefined}
- + Cancel
- ) + ); } diff --git a/app/routes/dns/dialogs/nameserver.tsx b/app/routes/dns/dialogs/nameserver.tsx index d7f6309..4f9fc3f 100644 --- a/app/routes/dns/dialogs/nameserver.tsx +++ b/app/routes/dns/dialogs/nameserver.tsx @@ -1,81 +1,81 @@ -import { RepoForkedIcon } from '@primer/octicons-react' -import { Form, useSubmit } from '@remix-run/react' -import { useState } from 'react' +import { RepoForkedIcon } from '@primer/octicons-react'; +import { Form, useSubmit } from 'react-router'; +import { useState } from 'react'; -import Dialog from '~/components/Dialog' -import Switch from '~/components/Switch' -import TextField from '~/components/TextField' -import Tooltip from '~/components/Tooltip' -import { cn } from '~/utils/cn' +import Dialog from '~/components/Dialog'; +import Switch from '~/components/Switch'; +import TextField from '~/components/TextField'; +import Tooltip from '~/components/Tooltip'; +import { cn } from '~/utils/cn'; interface Props { - nameservers: Record + nameservers: Record; } export default function AddNameserver({ nameservers }: Props) { - const submit = useSubmit() - const [split, setSplit] = useState(false) - const [ns, setNs] = useState('') - const [domain, setDomain] = useState('') + const submit = useSubmit(); + const [split, setSplit] = useState(false); + const [ns, setNs] = useState(''); + const [domain, setDomain] = useState(''); return ( - - Add nameserver - + Add nameserver - {close => ( + {(close) => ( <> - - Add nameserver - - - Nameserver - + Add nameserver + Nameserver Use this IPv4 or IPv6 address to resolve names. { - event.preventDefault() - if (!ns) return + event.preventDefault(); + if (!ns) return; if (split) { - const splitNs: Record = {} + const splitNs: Record = {}; for (const [key, value] of Object.entries(nameservers)) { - if (key === 'global') continue - splitNs[key] = value + if (key === 'global') continue; + splitNs[key] = value; } if (Object.keys(splitNs).includes(domain)) { - splitNs[domain].push(ns) + splitNs[domain].push(ns); } else { - splitNs[domain] = [ns] + splitNs[domain] = [ns]; } - submit({ - 'dns.nameservers.split': splitNs, - }, { - method: 'PATCH', - encType: 'application/json', - }) + submit( + { + 'dns.nameservers.split': splitNs, + }, + { + method: 'PATCH', + encType: 'application/json', + }, + ); } else { - const globalNs = nameservers.global - globalNs.push(ns) + const globalNs = nameservers.global; + globalNs.push(ns); - submit({ - 'dns.nameservers.global': globalNs, - }, { - method: 'PATCH', - encType: 'application/json', - }) + submit( + { + 'dns.nameservers.global': globalNs, + }, + { + method: 'PATCH', + encType: 'application/json', + }, + ); } - setNs('') - setDomain('') - setSplit(false) - close() + setNs(''); + setDomain(''); + setSplit(false); + close(); }} > - Split DNS - Only clients that support split DNS - (Tailscale v1.8 or later for most platforms) - will use this nameserver. Older clients - will ignore it. + Only clients that support split DNS (Tailscale v1.8 or + later for most platforms) will use this nameserver. + Older clients will ignore it.
@@ -116,40 +116,34 @@ export default function AddNameserver({ nameservers }: Props) { { setSplit(!split) }} + onChange={() => { + setSplit(!split); + }} />
- {split - ? ( - <> - - Domain - - - - Only single-label or fully-qualified queries - matching this suffix should use the nameserver. - - - ) - : undefined} + {split ? ( + <> + + Domain + + + + Only single-label or fully-qualified queries matching this + suffix should use the nameserver. + + + ) : undefined}
- + Cancel - + Add
@@ -158,5 +152,5 @@ export default function AddNameserver({ nameservers }: Props) { )} - ) + ); } diff --git a/app/routes/dns/overview.tsx b/app/routes/dns/overview.tsx index 6fa2a03..2dfcbed 100644 --- a/app/routes/dns/overview.tsx +++ b/app/routes/dns/overview.tsx @@ -1,27 +1,27 @@ -import { ActionFunctionArgs } from '@remix-run/node' -import { json, useLoaderData } from '@remix-run/react' +import { ActionFunctionArgs } from 'react-router'; +import { json, useLoaderData } from 'react-router'; -import Code from '~/components/Code' -import Notice from '~/components/Notice' -import { loadContext } from '~/utils/config/headplane' -import { loadConfig, patchConfig } from '~/utils/config/headscale' -import { getSession } from '~/utils/sessions' -import { useLiveData } from '~/utils/useLiveData' +import Code from '~/components/Code'; +import Notice from '~/components/Notice'; +import { loadContext } from '~/utils/config/headplane'; +import { loadConfig, patchConfig } from '~/utils/config/headscale'; +import { getSession } from '~/utils/sessions'; +import { useLiveData } from '~/utils/useLiveData'; -import DNS from './components/dns' -import Domains from './components/domains' -import MagicModal from './components/magic' -import Nameservers from './components/nameservers' -import RenameModal from './components/rename' +import DNS from './components/dns'; +import Domains from './components/domains'; +import MagicModal from './components/magic'; +import Nameservers from './components/nameservers'; +import RenameModal from './components/rename'; // We do not want to expose every config value export async function loader() { - const context = await loadContext() + const context = await loadContext(); if (!context.config.read) { - throw new Error('No configuration is available') + throw new Error('No configuration is available'); } - const config = await loadConfig() + const config = await loadConfig(); const dns = { prefixes: config.prefixes, magicDns: config.dns.magic_dns, @@ -32,65 +32,58 @@ export async function loader() { splitDns: config.dns.nameservers.split, searchDomains: config.dns.search_domains, extraRecords: config.dns.extra_records, - } + }; return { ...dns, ...context, - } + }; } export async function action({ request }: ActionFunctionArgs) { - const session = await getSession(request.headers.get('Cookie')) + const session = await getSession(request.headers.get('Cookie')); if (!session.has('hsApiKey')) { - return send({ success: false }, 401) + return send({ success: false }, 401); } - const context = await loadContext() + const context = await loadContext(); if (!context.config.write) { - return send({ success: false }, 403) + return send({ success: false }, 403); } - const data = await request.json() as Record - await patchConfig(data) + const data = (await request.json()) as Record; + await patchConfig(data); if (context.integration?.onConfigChange) { - await context.integration.onConfigChange(context.integration.context) + await context.integration.onConfigChange(context.integration.context); } - return { success: true } + return { success: true }; } export default function Page() { - useLiveData({ interval: 5000 }) - const data = useLoaderData() + useLiveData({ interval: 5000 }); + const data = useLoaderData(); - const allNs: Record = {} + const allNs: Record = {}; for (const key of Object.keys(data.splitDns)) { - allNs[key] = data.splitDns[key] + allNs[key] = data.splitDns[key]; } - allNs.global = data.nameservers + allNs.global = data.nameservers; return (
- {data.config.write - ? undefined - : ( - - The Headscale configuration is read-only. You cannot make changes to the configuration - - )} + {data.config.write ? undefined : ( + + The Headscale configuration is read-only. You cannot make changes to + the configuration + + )} - + - +

Magic DNS

- Automatically register domain names for each device - on the tailnet. Devices will be accessible at - {' '} + Automatically register domain names for each device on the tailnet. + Devices will be accessible at{' '} [device]. {data.baseDomain} - - {' '} + {' '} when Magic DNS is enabled.

- ) + ); } diff --git a/app/routes/machines/action.tsx b/app/routes/machines/action.tsx index 1144d92..c1e4a82 100644 --- a/app/routes/machines/action.tsx +++ b/app/routes/machines/action.tsx @@ -1,167 +1,208 @@ -import { ActionFunctionArgs } from '@remix-run/node' -import { del, post } from '~/utils/headscale' -import { getSession } from '~/utils/sessions' -import { send } from '~/utils/res' -import log from '~/utils/log' +import { ActionFunctionArgs } from 'react-router'; +import { del, post } from '~/utils/headscale'; +import { getSession } from '~/utils/sessions'; +import { send } from '~/utils/res'; +import log from '~/utils/log'; export async function menuAction(request: ActionFunctionArgs['request']) { - const session = await getSession(request.headers.get('Cookie')) + const session = await getSession(request.headers.get('Cookie')); if (!session.has('hsApiKey')) { - return send({ message: 'Unauthorized' }, { - status: 401, - }) + return send( + { message: 'Unauthorized' }, + { + status: 401, + }, + ); } - const data = await request.formData() + const data = await request.formData(); if (!data.has('_method') || !data.has('id')) { - return send({ message: 'No method or ID provided' }, { - status: 400, - }) + return send( + { message: 'No method or ID provided' }, + { + status: 400, + }, + ); } - const id = String(data.get('id')) - const method = String(data.get('_method')) + const id = String(data.get('id')); + const method = String(data.get('_method')); switch (method) { case 'delete': { - await del(`v1/node/${id}`, session.get('hsApiKey')!) - return { message: 'Machine removed' } + await del(`v1/node/${id}`, session.get('hsApiKey')!); + return { message: 'Machine removed' }; } case 'expire': { - await post(`v1/node/${id}/expire`, session.get('hsApiKey')!) - return { message: 'Machine expired' } + await post(`v1/node/${id}/expire`, session.get('hsApiKey')!); + return { message: 'Machine expired' }; } case 'rename': { if (!data.has('name')) { - return send({ message: 'No name provided' }, { - status: 400, - }) + return send( + { message: 'No name provided' }, + { + status: 400, + }, + ); } - const name = String(data.get('name')) + const name = String(data.get('name')); - await post(`v1/node/${id}/rename/${name}`, session.get('hsApiKey')!) - return { message: 'Machine renamed' } + await post(`v1/node/${id}/rename/${name}`, session.get('hsApiKey')!); + return { message: 'Machine renamed' }; } case 'routes': { if (!data.has('route') || !data.has('enabled')) { - return send({ message: 'No route or enabled provided' }, { - status: 400, - }) + return send( + { message: 'No route or enabled provided' }, + { + status: 400, + }, + ); } - const route = String(data.get('route')) - const enabled = data.get('enabled') === 'true' - const postfix = enabled ? 'enable' : 'disable' + const route = String(data.get('route')); + const enabled = data.get('enabled') === 'true'; + const postfix = enabled ? 'enable' : 'disable'; - await post(`v1/routes/${route}/${postfix}`, session.get('hsApiKey')!) - return { message: 'Route updated' } + await post(`v1/routes/${route}/${postfix}`, session.get('hsApiKey')!); + return { message: 'Route updated' }; } case 'exit-node': { if (!data.has('routes') || !data.has('enabled')) { - return send({ message: 'No route or enabled provided' }, { - status: 400, - }) + return send( + { message: 'No route or enabled provided' }, + { + status: 400, + }, + ); } - const routes = data.get('routes')?.toString().split(',') ?? [] - const enabled = data.get('enabled') === 'true' - const postfix = enabled ? 'enable' : 'disable' + const routes = data.get('routes')?.toString().split(',') ?? []; + const enabled = data.get('enabled') === 'true'; + const postfix = enabled ? 'enable' : 'disable'; - await Promise.all(routes.map(async (route) => { - await post(`v1/routes/${route}/${postfix}`, session.get('hsApiKey')!) - })) + await Promise.all( + routes.map(async (route) => { + await post(`v1/routes/${route}/${postfix}`, session.get('hsApiKey')!); + }), + ); - return { message: 'Exit node updated' } + return { message: 'Exit node updated' }; } case 'move': { if (!data.has('to')) { - return send({ message: 'No destination provided' }, { - status: 400, - }) + return send( + { message: 'No destination provided' }, + { + status: 400, + }, + ); } - const to = String(data.get('to')) + const to = String(data.get('to')); try { - await post(`v1/node/${id}/user?user=${to}`, session.get('hsApiKey')!) - return { message: `Moved node ${id} to ${to}` } + await post(`v1/node/${id}/user?user=${to}`, session.get('hsApiKey')!); + return { message: `Moved node ${id} to ${to}` }; } catch { - return send({ message: `Failed to move node ${id} to ${to}` }, { - status: 500, - }) + return send( + { message: `Failed to move node ${id} to ${to}` }, + { + status: 500, + }, + ); } } case 'tags': { - const tags = data.get('tags')?.toString() - .split(',') - .filter((tag) => tag.trim() !== '') - ?? [] + const tags = + data + .get('tags') + ?.toString() + .split(',') + .filter((tag) => tag.trim() !== '') ?? []; try { await post(`v1/node/${id}/tags`, session.get('hsApiKey')!, { tags, - }) + }); - return { message: 'Tags updated' } + return { message: 'Tags updated' }; } catch (error) { - log.debug('APIC', 'Failed to update tags: %s', error) - return send({ message: 'Failed to update tags' }, { - status: 500, - }) + log.debug('APIC', 'Failed to update tags: %s', error); + return send( + { message: 'Failed to update tags' }, + { + status: 500, + }, + ); } } case 'register': { - const key = data.get('mkey')?.toString() - const user = data.get('user')?.toString() + const key = data.get('mkey')?.toString(); + const user = data.get('user')?.toString(); if (!key) { - return send({ message: 'No machine key provided' }, { - status: 400, - }) + return send( + { message: 'No machine key provided' }, + { + status: 400, + }, + ); } if (!user) { - return send({ message: 'No user provided' }, { - status: 400, - }) + return send( + { message: 'No user provided' }, + { + status: 400, + }, + ); } try { - const qp = new URLSearchParams() - qp.append('user', user) - qp.append('key', key) + const qp = new URLSearchParams(); + qp.append('user', user); + qp.append('key', key); - const url = `v1/node/register?${qp.toString()}` + const url = `v1/node/register?${qp.toString()}`; await post(url, session.get('hsApiKey')!, { - user, key, - }) + user, + key, + }); return { success: true, - message: 'Machine registered' - } + message: 'Machine registered', + }; } catch { - return send({ - success: false, - message: 'Failed to register machine' - }, { - status: 500, - }) + return send( + { + success: false, + message: 'Failed to register machine', + }, + { + status: 500, + }, + ); } } default: { - return send({ message: 'Invalid method' }, { - status: 400, - }) + return send( + { message: 'Invalid method' }, + { + status: 400, + }, + ); } } } diff --git a/app/routes/machines/components/machine.tsx b/app/routes/machines/components/machine.tsx index ca408f5..84f04d5 100644 --- a/app/routes/machines/components/machine.tsx +++ b/app/routes/machines/components/machine.tsx @@ -1,68 +1,69 @@ -import { ChevronDownIcon, CopyIcon } from '@primer/octicons-react' -import { Link } from '@remix-run/react' +import { ChevronDownIcon, CopyIcon } from '@primer/octicons-react'; +import { Link } from 'react-router'; -import Menu from '~/components/Menu' -import StatusCircle from '~/components/StatusCircle' -import { toast } from '~/components/Toaster' -import { Machine, Route, User } from '~/types' -import { cn } from '~/utils/cn' +import Menu from '~/components/Menu'; +import StatusCircle from '~/components/StatusCircle'; +import { toast } from '~/components/Toaster'; +import { Machine, Route, User } from '~/types'; +import { cn } from '~/utils/cn'; -import MenuOptions from './menu' +import MenuOptions from './menu'; interface Props { - readonly machine: Machine - readonly routes: Route[] - readonly users: User[] - readonly magic?: string + readonly machine: Machine; + readonly routes: Route[]; + readonly users: User[]; + readonly magic?: string; } export default function MachineRow({ machine, routes, magic, users }: Props) { - const expired = machine.expiry === '0001-01-01 00:00:00' - || machine.expiry === '0001-01-01T00:00:00Z' - || machine.expiry === null - ? false - : new Date(machine.expiry).getTime() < Date.now() + const expired = + machine.expiry === '0001-01-01 00:00:00' || + machine.expiry === '0001-01-01T00:00:00Z' || + machine.expiry === null + ? false + : new Date(machine.expiry).getTime() < Date.now(); - const tags = [ - ...machine.forcedTags, - ...machine.validTags, - ] + const tags = [...machine.forcedTags, ...machine.validTags]; if (expired) { - tags.unshift('Expired') + tags.unshift('Expired'); } let prefix = magic?.startsWith('[user]') ? magic.replace('[user]', machine.user.name) - : magic + : magic; // This is much easier with Object.groupBy but it's too new for us - const { exit, subnet, subnetApproved } = routes.reduce((acc, route) => { - if (route.prefix === '::/0' || route.prefix === '0.0.0.0/0') { - acc.exit.push(route) - return acc - } + const { exit, subnet, subnetApproved } = routes.reduce( + (acc, route) => { + if (route.prefix === '::/0' || route.prefix === '0.0.0.0/0') { + acc.exit.push(route); + return acc; + } - if (route.enabled) { - acc.subnetApproved.push(route) - return acc - } + if (route.enabled) { + acc.subnetApproved.push(route); + return acc; + } - acc.subnet.push(route) - return acc - }, { exit: [], subnetApproved: [], subnet: [] }) + acc.subnet.push(route); + return acc; + }, + { exit: [], subnetApproved: [], subnet: [] }, + ); const exitEnabled = useMemo(() => { - if (exit.length !== 2) return false - return exit[0].enabled && exit[1].enabled - }, [exit]) + if (exit.length !== 2) return false; + return exit[0].enabled && exit[1].enabled; + }, [exit]); if (exitEnabled) { - tags.unshift('Exit Node') + tags.unshift('Exit Node'); } if (subnetApproved.length > 0) { - tags.unshift('Subnets') + tags.unshift('Subnets'); } return ( @@ -71,15 +72,13 @@ export default function MachineRow({ machine, routes, magic, users }: Props) { className="hover:bg-zinc-100 dark:hover:bg-zinc-800 group" > - -

+

{machine.givenName}

@@ -87,7 +86,7 @@ export default function MachineRow({ machine, routes, magic, users }: Props) { {machine.name}

- {tags.map(tag => ( + {tags.map((tag) => ( - {machine.ipAddresses.map(ip => ( + {machine.ipAddresses.map((ip) => ( { - await navigator.clipboard.writeText(ip) - toast('Copied IP address to clipboard') + await navigator.clipboard.writeText(ip); + toast('Copied IP address to clipboard'); }} > {ip} ))} - {magic - ? ( - { - const ip = `${machine.givenName}.${prefix}` - await navigator.clipboard.writeText(ip) - toast('Copied hostname to clipboard') - }} - > - {machine.givenName} - . - {prefix} - - - ) - : undefined} + {magic ? ( + { + const ip = `${machine.givenName}.${prefix}`; + await navigator.clipboard.writeText(ip); + toast('Copied hostname to clipboard'); + }} + > + {machine.givenName}.{prefix} + + + ) : undefined}
- {machine.online && !expired ? 'Connected' - : new Date( - machine.lastSeen, - ).toLocaleString()} + : new Date(machine.lastSeen).toLocaleString()}

@@ -180,5 +174,5 @@ export default function MachineRow({ machine, routes, magic, users }: Props) { /> - ) + ); } diff --git a/app/routes/machines/components/menu.tsx b/app/routes/machines/components/menu.tsx index a519e69..1b870a2 100644 --- a/app/routes/machines/components/menu.tsx +++ b/app/routes/machines/components/menu.tsx @@ -1,73 +1,54 @@ -import { KebabHorizontalIcon } from '@primer/octicons-react' -import { ReactNode, useState } from 'react' +import { KebabHorizontalIcon } from '@primer/octicons-react'; +import { ReactNode, useState } from 'react'; -import MenuComponent from '~/components/Menu' -import { Machine, Route, User } from '~/types' -import { cn } from '~/utils/cn' +import MenuComponent from '~/components/Menu'; +import { Machine, Route, User } from '~/types'; +import { cn } from '~/utils/cn'; -import Delete from '../dialogs/delete' -import Expire from '../dialogs/expire' -import Move from '../dialogs/move' -import Rename from '../dialogs/rename' -import Routes from '../dialogs/routes' -import Tags from '../dialogs/tags' +import Delete from '../dialogs/delete'; +import Expire from '../dialogs/expire'; +import Move from '../dialogs/move'; +import Rename from '../dialogs/rename'; +import Routes from '../dialogs/routes'; +import Tags from '../dialogs/tags'; interface MenuProps { - machine: Machine - routes: Route[] - users: User[] - magic?: string - buttonChild?: ReactNode + machine: Machine; + routes: Route[]; + users: User[]; + magic?: string; + buttonChild?: ReactNode; } -export default function Menu({ machine, routes, magic, users, buttonChild }: MenuProps) { - const renameState = useState(false) - const expireState = useState(false) - const removeState = useState(false) - const routesState = useState(false) - const moveState = useState(false) - const tagsState = useState(false) +export default function Menu({ + machine, + routes, + magic, + users, + buttonChild, +}: MenuProps) { + const renameState = useState(false); + const expireState = useState(false); + const removeState = useState(false); + const routesState = useState(false); + const moveState = useState(false); + const tagsState = useState(false); - const expired = machine.expiry === '0001-01-01 00:00:00' - || machine.expiry === '0001-01-01T00:00:00Z' - || machine.expiry === null - ? false - : new Date(machine.expiry).getTime() < Date.now() + const expired = + machine.expiry === '0001-01-01 00:00:00' || + machine.expiry === '0001-01-01T00:00:00Z' || + machine.expiry === null + ? false + : new Date(machine.expiry).getTime() < Date.now(); return ( <> - - - {expired - ? undefined - : ( - - )} - - - + + + {expired ? undefined : } + + + {buttonChild ?? ( @@ -94,13 +75,11 @@ export default function Menu({ machine, routes, magic, users, buttonChild }: Men Change owner - {expired - ? undefined - : ( - - Expire - - )} + {expired ? undefined : ( + + Expire + + )} - ) + ); } diff --git a/app/routes/machines/dialogs/delete.tsx b/app/routes/machines/dialogs/delete.tsx index dfb03f6..addb7ce 100644 --- a/app/routes/machines/dialogs/delete.tsx +++ b/app/routes/machines/dialogs/delete.tsx @@ -1,46 +1,39 @@ -import { Form, useSubmit } from '@remix-run/react' -import { type Dispatch, type SetStateAction } from 'react' +import { Form, useSubmit } from 'react-router'; +import { type Dispatch, type SetStateAction } from 'react'; -import Dialog from '~/components/Dialog' -import { type Machine } from '~/types' -import { cn } from '~/utils/cn' +import Dialog from '~/components/Dialog'; +import { type Machine } from '~/types'; +import { cn } from '~/utils/cn'; interface DeleteProps { - readonly machine: Machine - readonly state: [boolean, Dispatch>] + readonly machine: Machine; + readonly state: [boolean, Dispatch>]; } export default function Delete({ machine, state }: DeleteProps) { - const submit = useSubmit() + const submit = useSubmit(); return ( - {close => ( + {(close) => ( <> - - Remove - {' '} - {machine.givenName} - + Remove {machine.givenName} - This machine will be permanently removed from - your network. To re-add it, you will need to - reauthenticate to your tailnet from the device. + This machine will be permanently removed from your network. To + re-add it, you will need to reauthenticate to your tailnet from + the device. { - submit(e.currentTarget) + submit(e.currentTarget); }} >
- + Cancel
- ) + ); } diff --git a/app/routes/machines/dialogs/expire.tsx b/app/routes/machines/dialogs/expire.tsx index 7af827d..a1347a3 100644 --- a/app/routes/machines/dialogs/expire.tsx +++ b/app/routes/machines/dialogs/expire.tsx @@ -1,46 +1,38 @@ -import { Form, useSubmit } from '@remix-run/react' -import { type Dispatch, type SetStateAction } from 'react' +import { Form, useSubmit } from 'react-router'; +import { type Dispatch, type SetStateAction } from 'react'; -import Dialog from '~/components/Dialog' -import { type Machine } from '~/types' -import { cn } from '~/utils/cn' +import Dialog from '~/components/Dialog'; +import { type Machine } from '~/types'; +import { cn } from '~/utils/cn'; interface ExpireProps { - readonly machine: Machine - readonly state: [boolean, Dispatch>] + readonly machine: Machine; + readonly state: [boolean, Dispatch>]; } export default function Expire({ machine, state }: ExpireProps) { - const submit = useSubmit() + const submit = useSubmit(); return ( - {close => ( + {(close) => ( <> - - Expire - {' '} - {machine.givenName} - + Expire {machine.givenName} - This will disconnect the machine from your Tailnet. - In order to reconnect, you will need to re-authenticate - from the device. + This will disconnect the machine from your Tailnet. In order to + reconnect, you will need to re-authenticate from the device. { - submit(e.currentTarget) + submit(e.currentTarget); }} >
- + Cancel
- ) + ); } diff --git a/app/routes/machines/dialogs/move.tsx b/app/routes/machines/dialogs/move.tsx index 3fadc92..8b76db5 100644 --- a/app/routes/machines/dialogs/move.tsx +++ b/app/routes/machines/dialogs/move.tsx @@ -1,39 +1,35 @@ -import { Form, useSubmit } from '@remix-run/react' -import { type Dispatch, type SetStateAction, useState } from 'react' +import { Form, useSubmit } from 'react-router'; +import { type Dispatch, type SetStateAction, useState } from 'react'; -import Code from '~/components/Code' -import Dialog from '~/components/Dialog' -import Select from '~/components/Select' -import { type Machine, User } from '~/types' +import Code from '~/components/Code'; +import Dialog from '~/components/Dialog'; +import Select from '~/components/Select'; +import { type Machine, User } from '~/types'; interface MoveProps { - readonly machine: Machine - readonly users: User[] - readonly state: [boolean, Dispatch>] - readonly magic?: string + readonly machine: Machine; + readonly users: User[]; + readonly state: [boolean, Dispatch>]; + readonly magic?: string; } export default function Move({ machine, state, magic, users }: MoveProps) { - const [owner, setOwner] = useState(machine.user.name) - const submit = useSubmit() + const [owner, setOwner] = useState(machine.user.name); + const submit = useSubmit(); return ( - {close => ( + {(close) => ( <> - - Change the owner of - {' '} - {machine.givenName} - + Change the owner of {machine.givenName} The owner of the machine is the user associated with it. { - submit(e.currentTarget) + submit(e.currentTarget); }} > @@ -44,37 +40,26 @@ export default function Move({ machine, state, magic, users }: MoveProps) { placeholder="Select a user" state={[owner, setOwner]} > - {users.map(user => ( + {users.map((user) => ( {user.name} ))} - {magic - ? ( -

- This machine is accessible by the hostname - {' '} - - {machine.givenName} - . - {magic} - - . -

- ) - : undefined} + {magic ? ( +

+ This machine is accessible by the hostname{' '} + + {machine.givenName}.{magic} + + . +

+ ) : undefined}
- + Cancel - + Change owner
@@ -83,5 +68,5 @@ export default function Move({ machine, state, magic, users }: MoveProps) { )}
- ) + ); } diff --git a/app/routes/machines/dialogs/new.tsx b/app/routes/machines/dialogs/new.tsx index 635ad1b..7a34f62 100644 --- a/app/routes/machines/dialogs/new.tsx +++ b/app/routes/machines/dialogs/new.tsx @@ -1,74 +1,73 @@ -import { Form, useFetcher, Link } from '@remix-run/react' -import { Dispatch, SetStateAction, useState, useEffect } from 'react' -import { PlusIcon, ServerIcon, KeyIcon } from '@primer/octicons-react' -import { cn } from '~/utils/cn' +import { Form, useFetcher, Link } from 'react-router'; +import { Dispatch, SetStateAction, useState, useEffect } from 'react'; +import { PlusIcon, ServerIcon, KeyIcon } from '@primer/octicons-react'; +import { cn } from '~/utils/cn'; -import Code from '~/components/Code' -import Dialog from '~/components/Dialog' -import TextField from '~/components/TextField' -import Select from '~/components/Select' -import Menu from '~/components/Menu' -import Spinner from '~/components/Spinner' -import { toast } from '~/components/Toaster' -import { Machine, User } from '~/types' +import Code from '~/components/Code'; +import Dialog from '~/components/Dialog'; +import TextField from '~/components/TextField'; +import Select from '~/components/Select'; +import Menu from '~/components/Menu'; +import Spinner from '~/components/Spinner'; +import { toast } from '~/components/Toaster'; +import { Machine, User } from '~/types'; export interface NewProps { - server: string - users: User[] + server: string; + users: User[]; } export default function New(data: NewProps) { - const fetcher = useFetcher<{ success?: boolean }>() - const mkeyState = useState(false) - const [mkey, setMkey] = useState('') - const [user, setUser] = useState('') - const [toasted, setToasted] = useState(false) + const fetcher = useFetcher<{ success?: boolean }>(); + const mkeyState = useState(false); + const [mkey, setMkey] = useState(''); + const [user, setUser] = useState(''); + const [toasted, setToasted] = useState(false); useEffect(() => { if (!fetcher.data || toasted) { - return + return; } if (fetcher.data.success) { - toast('Registered new machine') + toast('Registered new machine'); } else { - toast('Failed to register machine due to an invalid key') + toast('Failed to register machine due to an invalid key'); } - setToasted(true) - }, [fetcher.data, toasted, mkey]) + setToasted(true); + }, [fetcher.data, toasted, mkey]); return ( <> - {close => ( + {(close) => ( <> - - Register Machine Key - - - The machine key is given when you run - {' '} + Register Machine Key + + The machine key is given when you run{' '} tailscale up --login-server= {data.server} - - {' '} + {' '} on your device. - { - fetcher.submit(e.currentTarget) - close() - }}> + { + fetcher.submit(e.currentTarget); + close(); + }} + > -
- +
+ Cancel - {fetcher.state === 'idle' - ? undefined - : ( - - )} + {fetcher.state === 'idle' ? undefined : ( + + )} Register
@@ -118,17 +114,17 @@ export default function New(data: NewProps) { - + Register Machine Key - + Generate Pre-auth Key - ) + ); } diff --git a/app/routes/machines/dialogs/rename.tsx b/app/routes/machines/dialogs/rename.tsx index 36bc65c..7dda524 100644 --- a/app/routes/machines/dialogs/rename.tsx +++ b/app/routes/machines/dialogs/rename.tsx @@ -1,39 +1,37 @@ -import { Form, useSubmit } from '@remix-run/react' -import { type Dispatch, type SetStateAction, useState } from 'react' +import { Form, useSubmit } from 'react-router'; +import { type Dispatch, type SetStateAction, useState } from 'react'; -import Code from '~/components/Code' -import Dialog from '~/components/Dialog' -import TextField from '~/components/TextField' -import { type Machine } from '~/types' +import Code from '~/components/Code'; +import Dialog from '~/components/Dialog'; +import TextField from '~/components/TextField'; +import { type Machine } from '~/types'; interface RenameProps { - readonly machine: Machine - readonly state: [boolean, Dispatch>] - readonly magic?: string + readonly machine: Machine; + readonly state: [boolean, Dispatch>]; + readonly magic?: string; } export default function Rename({ machine, state, magic }: RenameProps) { - const [name, setName] = useState(machine.givenName) - const submit = useSubmit() + const [name, setName] = useState(machine.givenName); + const submit = useSubmit(); return ( - {close => ( + {(close) => ( <> - Edit machine name for - {' '} - {machine.givenName} + Edit machine name for {machine.givenName} - This name is shown in the admin panel, in Tailscale clients, - and used when generating MagicDNS names. + This name is shown in the admin panel, in Tailscale clients, and + used when generating MagicDNS names. { - submit(e.currentTarget) + submit(e.currentTarget); }} > @@ -45,49 +43,30 @@ export default function Rename({ machine, state, magic }: RenameProps) { state={[name, setName]} className="my-2" /> - {magic - ? ( - name.length > 0 && name !== machine.givenName - ? ( -

- This machine will be accessible by the hostname - {' '} - - {name.toLowerCase().replaceAll(/\s+/g, '-')} - - {'. '} - The hostname - {' '} - - {machine.givenName} - - {' '} - will no longer point to this machine. -

- ) - : ( -

- This machine is accessible by the hostname - {' '} - - {machine.givenName} - - . -

- ) - ) - : undefined} + {magic ? ( + name.length > 0 && name !== machine.givenName ? ( +

+ This machine will be accessible by the hostname{' '} + + {name.toLowerCase().replaceAll(/\s+/g, '-')} + + {'. '} + The hostname{' '} + {machine.givenName} will no + longer point to this machine. +

+ ) : ( +

+ This machine is accessible by the hostname{' '} + {machine.givenName}. +

+ ) + ) : undefined}
- + Cancel - + Rename
@@ -96,5 +75,5 @@ export default function Rename({ machine, state, magic }: RenameProps) { )}
- ) + ); } diff --git a/app/routes/machines/dialogs/routes.tsx b/app/routes/machines/dialogs/routes.tsx index 7414036..7c2bce6 100644 --- a/app/routes/machines/dialogs/routes.tsx +++ b/app/routes/machines/dialogs/routes.tsx @@ -1,55 +1,53 @@ -import { useFetcher } from '@remix-run/react' -import { Dispatch, SetStateAction, useMemo } from 'react' +import { useFetcher } from 'react-router'; +import { Dispatch, SetStateAction, useMemo } from 'react'; -import Dialog from '~/components/Dialog' -import Switch from '~/components/Switch' -import Link from '~/components/Link' -import { Machine, Route } from '~/types' -import { cn } from '~/utils/cn' +import Dialog from '~/components/Dialog'; +import Switch from '~/components/Switch'; +import Link from '~/components/Link'; +import { Machine, Route } from '~/types'; +import { cn } from '~/utils/cn'; interface RoutesProps { - readonly machine: Machine - readonly routes: Route[] - readonly state: [boolean, Dispatch>] + readonly machine: Machine; + readonly routes: Route[]; + readonly state: [boolean, Dispatch>]; } // TODO: Support deleting routes export default function Routes({ machine, routes, state }: RoutesProps) { - const fetcher = useFetcher() + const fetcher = useFetcher(); // This is much easier with Object.groupBy but it's too new for us - const { exit, subnet } = routes.reduce((acc, route) => { - if (route.prefix === '::/0' || route.prefix === '0.0.0.0/0') { - acc.exit.push(route) - return acc - } + const { exit, subnet } = routes.reduce( + (acc, route) => { + if (route.prefix === '::/0' || route.prefix === '0.0.0.0/0') { + acc.exit.push(route); + return acc; + } - acc.subnet.push(route) - return acc - }, { exit: [], subnet: [] }) + acc.subnet.push(route); + return acc; + }, + { exit: [], subnet: [] }, + ); const exitEnabled = useMemo(() => { - if (exit.length !== 2) return false - return exit[0].enabled && exit[1].enabled - }, [exit]) + if (exit.length !== 2) return false; + return exit[0].enabled && exit[1].enabled; + }, [exit]); return ( - {close => ( + {(close) => ( <> - Edit route settings of - {' '} - {machine.givenName} + Edit route settings of {machine.givenName} - - Subnet routes - + Subnet routes - Connect to devices you can't install Tailscale on - by advertising IP ranges as subnet routes. - {' '} + Connect to devices you can't install Tailscale on by + advertising IP ranges as subnet routes.{' '} -
- {subnet.length === 0 - ? ( -
-

- No routes are advertised on this machine. -

-
- ) - : undefined} - {subnet.map(route => ( + {subnet.length === 0 ? ( +
+

No routes are advertised on this machine.

+
+ ) : undefined} + {subnet.map((route) => (
-

- {route.prefix} -

+

{route.prefix}

{ - const form = new FormData() - form.set('id', machine.id) - form.set('_method', 'routes') - form.set('route', route.id) + const form = new FormData(); + form.set('id', machine.id); + form.set('_method', 'routes'); + form.set('route', route.id); - form.set('enabled', String(checked)) + form.set('enabled', String(checked)); fetcher.submit(form, { method: 'POST', - }) + }); }} />
))}
- - Exit nodes - + Exit nodes - Allow your network to route internet traffic through this machine. - {' '} + Allow your network to route internet traffic through this machine.{' '} -
- {exit.length === 0 - ? ( -
-

- This machine is not an exit node. -

-
- ) : ( -
-

- Use as exit node -

- { - const form = new FormData() - form.set('id', machine.id) - form.set('_method', 'exit-node') - form.set('routes', exit.map(route => route.id).join(',')) + {exit.length === 0 ? ( +
+

This machine is not an exit node.

+
+ ) : ( +
+

Use as exit node

+ { + const form = new FormData(); + form.set('id', machine.id); + form.set('_method', 'exit-node'); + form.set( + 'routes', + exit.map((route) => route.id).join(','), + ); - form.set('enabled', String(checked)) - fetcher.submit(form, { - method: 'POST', - }) - }} - /> -
- )} + form.set('enabled', String(checked)); + fetcher.submit(form, { + method: 'POST', + }); + }} + /> +
+ )}
- ) + ); } diff --git a/app/routes/machines/dialogs/tags.tsx b/app/routes/machines/dialogs/tags.tsx index 9adccc6..bb5a76b 100644 --- a/app/routes/machines/dialogs/tags.tsx +++ b/app/routes/machines/dialogs/tags.tsx @@ -1,51 +1,44 @@ -import { PlusIcon, XIcon } from '@primer/octicons-react' -import { Form, useSubmit } from '@remix-run/react' -import { Dispatch, SetStateAction, useState } from 'react' -import { Button, Input } from 'react-aria-components' +import { PlusIcon, XIcon } from '@primer/octicons-react'; +import { Form, useSubmit } from 'react-router'; +import { Dispatch, SetStateAction, useState } from 'react'; +import { Button, Input } from 'react-aria-components'; -import Dialog from '~/components/Dialog' -import Link from '~/components/Link' -import { Machine } from '~/types' -import { cn } from '~/utils/cn' +import Dialog from '~/components/Dialog'; +import Link from '~/components/Link'; +import { Machine } from '~/types'; +import { cn } from '~/utils/cn'; interface TagsProps { - readonly machine: Machine - readonly state: [boolean, Dispatch>] + readonly machine: Machine; + readonly state: [boolean, Dispatch>]; } export default function Tags({ machine, state }: TagsProps) { - const [tags, setTags] = useState(machine.forcedTags) - const [tag, setTag] = useState('') - const submit = useSubmit() + const [tags, setTags] = useState(machine.forcedTags); + const [tag, setTag] = useState(''); + const submit = useSubmit(); return ( - {close => ( + {(close) => ( <> - - Edit ACL tags for - {' '} - {machine.givenName} - + Edit ACL tags for {machine.givenName} ACL tags can be used to reference machines in your ACL policies. - See the - {' '} - + See the{' '} Tailscale documentation - - {' '} + {' '} for more information. { - submit(e.currentTarget) + submit(e.currentTarget); }} > @@ -58,21 +51,18 @@ export default function Tags({ machine, state }: TagsProps) { )} >
- {tags.length === 0 - ? ( -
-

- No tags are set on this machine. -

-
- ) - : tags.map(item => ( + {tags.length === 0 ? ( +
+

No tags are set on this machine.

+
+ ) : ( + tags.map((item) => (
{ - setTags(tags.filter(tag => tag !== item)) + setTags(tags.filter((tag) => tag !== item)); }} >
- ))} + )) + )}
0 && !tag.startsWith('tag:') - && 'outline outline-red-500', + tag.length > 0 && + !tag.startsWith('tag:') && + 'outline outline-red-500', )} > { - setTag(e.currentTarget.value) + setTag(e.currentTarget.value); }} />
- + Cancel - + Save
@@ -157,5 +143,5 @@ export default function Tags({ machine, state }: TagsProps) { )}
- ) + ); } diff --git a/app/routes/machines/machine.tsx b/app/routes/machines/machine.tsx index 3ee7644..8567a82 100644 --- a/app/routes/machines/machine.tsx +++ b/app/routes/machines/machine.tsx @@ -1,40 +1,46 @@ -import { ActionFunctionArgs, LoaderFunctionArgs } from '@remix-run/node' -import { Link as RemixLink, useLoaderData } from '@remix-run/react' -import { InfoIcon, GearIcon, CheckCircleIcon, SkipIcon, PersonIcon } from '@primer/octicons-react' -import { useMemo, useState } from 'react' +import { ActionFunctionArgs, LoaderFunctionArgs } from 'react-router'; +import { Link as RemixLink, useLoaderData } from 'react-router'; +import { + InfoIcon, + GearIcon, + CheckCircleIcon, + SkipIcon, + PersonIcon, +} from '@primer/octicons-react'; +import { useMemo, useState } from 'react'; -import Attribute from '~/components/Attribute' -import Button from '~/components/Button' -import Card from '~/components/Card' -import Menu from '~/components/Menu' -import Tooltip from '~/components/Tooltip' -import StatusCircle from '~/components/StatusCircle' -import { Machine, Route, User } from '~/types' -import { cn } from '~/utils/cn' -import { loadContext } from '~/utils/config/headplane' -import { loadConfig } from '~/utils/config/headscale' -import { pull } from '~/utils/headscale' -import { getSession } from '~/utils/sessions' -import { useLiveData } from '~/utils/useLiveData' -import Link from '~/components/Link' +import Attribute from '~/components/Attribute'; +import Button from '~/components/Button'; +import Card from '~/components/Card'; +import Menu from '~/components/Menu'; +import Tooltip from '~/components/Tooltip'; +import StatusCircle from '~/components/StatusCircle'; +import { Machine, Route, User } from '~/types'; +import { cn } from '~/utils/cn'; +import { loadContext } from '~/utils/config/headplane'; +import { loadConfig } from '~/utils/config/headscale'; +import { pull } from '~/utils/headscale'; +import { getSession } from '~/utils/sessions'; +import { useLiveData } from '~/utils/useLiveData'; +import Link from '~/components/Link'; -import { menuAction } from './action' -import MenuOptions from './components/menu' -import Routes from './dialogs/routes' +import { menuAction } from './action'; +import MenuOptions from './components/menu'; +import Routes from './dialogs/routes'; export async function loader({ request, params }: LoaderFunctionArgs) { - const session = await getSession(request.headers.get('Cookie')) + const session = await getSession(request.headers.get('Cookie')); if (!params.id) { - throw new Error('No machine ID provided') + throw new Error('No machine ID provided'); } - const context = await loadContext() - let magic: string | undefined + const context = await loadContext(); + let magic: string | undefined; if (context.config.read) { - const config = await loadConfig() + const config = await loadConfig(); if (config.dns.magic_dns) { - magic = config.dns.base_domain + magic = config.dns.base_domain; } } @@ -42,110 +48,106 @@ export async function loader({ request, params }: LoaderFunctionArgs) { pull<{ node: Machine }>(`v1/node/${params.id}`, session.get('hsApiKey')!), pull<{ routes: Route[] }>('v1/routes', session.get('hsApiKey')!), pull<{ users: User[] }>('v1/user', session.get('hsApiKey')!), - ]) + ]); return { machine: machine.node, - routes: routes.routes.filter(route => route.node.id === params.id), + routes: routes.routes.filter((route) => route.node.id === params.id), users: users.users, magic, - } + }; } export async function action({ request }: ActionFunctionArgs) { - return menuAction(request) + return menuAction(request); } export default function Page() { - const { machine, magic, routes, users } = useLoaderData() - const routesState = useState(false) - useLiveData({ interval: 1000 }) + const { machine, magic, routes, users } = useLoaderData(); + const routesState = useState(false); + useLiveData({ interval: 1000 }); - const expired = machine.expiry === '0001-01-01 00:00:00' - || machine.expiry === '0001-01-01T00:00:00Z' - || machine.expiry === null - ? false - : new Date(machine.expiry).getTime() < Date.now() + const expired = + machine.expiry === '0001-01-01 00:00:00' || + machine.expiry === '0001-01-01T00:00:00Z' || + machine.expiry === null + ? false + : new Date(machine.expiry).getTime() < Date.now(); - const tags = [ - ...machine.forcedTags, - ...machine.validTags, - ] + const tags = [...machine.forcedTags, ...machine.validTags]; if (expired) { - tags.unshift('Expired') + tags.unshift('Expired'); } // This is much easier with Object.groupBy but it's too new for us - const { exit, subnet, subnetApproved } = routes.reduce((acc, route) => { - if (route.prefix === '::/0' || route.prefix === '0.0.0.0/0') { - acc.exit.push(route) - return acc - } + const { exit, subnet, subnetApproved } = routes.reduce( + (acc, route) => { + if (route.prefix === '::/0' || route.prefix === '0.0.0.0/0') { + acc.exit.push(route); + return acc; + } - if (route.enabled) { - acc.subnetApproved.push(route) - return acc - } + if (route.enabled) { + acc.subnetApproved.push(route); + return acc; + } - acc.subnet.push(route) - return acc - }, { exit: [], subnetApproved: [], subnet: [] }) + acc.subnet.push(route); + return acc; + }, + { exit: [], subnetApproved: [], subnet: [] }, + ); const exitEnabled = useMemo(() => { - if (exit.length !== 2) return false - return exit[0].enabled && exit[1].enabled - }, [exit]) + if (exit.length !== 2) return false; + return exit[0].enabled && exit[1].enabled; + }, [exit]); if (exitEnabled) { - tags.unshift('Exit Node') + tags.unshift('Exit Node'); } if (subnetApproved.length > 0) { - tags.unshift('Subnets') + tags.unshift('Subnets'); } return (

- + All Machines - - / - + / {machine.givenName}

-
+
-

- {machine.givenName} -

+

{machine.givenName}

+ Machine Settings @@ -166,21 +168,21 @@ export default function Page() {
-
+
{machine.user.name}
-

- Status -

+

Status

- {tags.map(tag => ( + {tags.map((tag) => (
-

- Subnets & Routing -

- +

Subnets & Routing

+

- Subnets let you expose physical network routes onto Tailscale. - {' '} + Subnets let you expose physical network routes onto Tailscale.{' '}

-
@@ -236,22 +228,17 @@ export default function Page() { - Traffic to these routes are being - routed through this machine. + Traffic to these routes are being routed through this machine.
{subnetApproved.length === 0 ? ( - - — - + — ) : (
    - {subnetApproved.map(route => ( -
  • - {route.prefix} -
  • + {subnetApproved.map((route) => ( +
  • {route.prefix}
  • ))}
)} @@ -276,23 +263,18 @@ export default function Page() { - This machine is advertising these routes, - but they must be approved before traffic - will be routed to them. + This machine is advertising these routes, but they must be + approved before traffic will be routed to them.
{subnet.length === 0 ? ( - - — - + — ) : (
    - {subnet.map(route => ( -
  • - {route.prefix} -
  • + {subnet.map((route) => ( +
  • {route.prefix}
  • ))}
)} @@ -317,16 +299,13 @@ export default function Page() { - Whether this machine can act as an - exit node for your tailnet. + Whether this machine can act as an exit node for your tailnet.
{exit.length === 0 ? ( - - — - + — ) : exitEnabled ? ( @@ -352,19 +331,13 @@ export default function Page() {
-

- Machine Details -

+

Machine Details

- + - {magic - ? ( - - ) - : undefined} + {magic ? ( + + ) : undefined}
- ) + ); } diff --git a/app/routes/machines/overview.tsx b/app/routes/machines/overview.tsx index 2467d45..ea00cf9 100644 --- a/app/routes/machines/overview.tsx +++ b/app/routes/machines/overview.tsx @@ -1,41 +1,41 @@ -import { InfoIcon } from '@primer/octicons-react' -import { ActionFunctionArgs, LoaderFunctionArgs } from '@remix-run/node' -import { useLoaderData } from '@remix-run/react' -import { Button, Tooltip, TooltipTrigger } from 'react-aria-components' +import { InfoIcon } from '@primer/octicons-react'; +import { ActionFunctionArgs, LoaderFunctionArgs } from 'react-router'; +import { useLoaderData } from 'react-router'; +import { Button, Tooltip, TooltipTrigger } from 'react-aria-components'; -import Code from '~/components/Code' -import Link from '~/components/Link' -import { cn } from '~/utils/cn' -import { loadContext } from '~/utils/config/headplane' -import { loadConfig } from '~/utils/config/headscale' -import { pull } from '~/utils/headscale' -import { getSession } from '~/utils/sessions' -import { useLiveData } from '~/utils/useLiveData' -import type { Machine, Route, User } from '~/types' +import Code from '~/components/Code'; +import Link from '~/components/Link'; +import { cn } from '~/utils/cn'; +import { loadContext } from '~/utils/config/headplane'; +import { loadConfig } from '~/utils/config/headscale'; +import { pull } from '~/utils/headscale'; +import { getSession } from '~/utils/sessions'; +import { useLiveData } from '~/utils/useLiveData'; +import type { Machine, Route, User } from '~/types'; -import { menuAction } from './action' -import MachineRow from './components/machine' -import NewMachine from './dialogs/new' +import { menuAction } from './action'; +import MachineRow from './components/machine'; +import NewMachine from './dialogs/new'; export async function loader({ request }: LoaderFunctionArgs) { - const session = await getSession(request.headers.get('Cookie')) + const session = await getSession(request.headers.get('Cookie')); const [machines, routes, users] = await Promise.all([ pull<{ nodes: Machine[] }>('v1/node', session.get('hsApiKey')!), pull<{ routes: Route[] }>('v1/routes', session.get('hsApiKey')!), pull<{ users: User[] }>('v1/user', session.get('hsApiKey')!), - ]) + ]); - const context = await loadContext() - let magic: string | undefined + const context = await loadContext(); + let magic: string | undefined; if (context.config.read) { - const config = await loadConfig() + const config = await loadConfig(); if (config.dns.magic_dns) { - magic = config.dns.base_domain + magic = config.dns.base_domain; } if (config.dns.use_username_in_magic_dns) { - magic = `[user].${magic}` + magic = `[user].${magic}`; } } @@ -46,25 +46,24 @@ export async function loader({ request }: LoaderFunctionArgs) { magic, server: context.headscaleUrl, publicServer: context.headscalePublicUrl, - } + }; } export async function action({ request }: ActionFunctionArgs) { - return menuAction(request) + return menuAction(request); } export default function Page() { - useLiveData({ interval: 3000 }) - const data = useLoaderData() + useLiveData({ interval: 3000 }); + const data = useLoaderData(); return ( <>
-

Machines

-

- Manage the devices connected to your Tailnet. - {' '} +

Machines

+

+ Manage the devices connected to your Tailnet.{' '}

Addresses - {data.magic - ? ( - - - + + - Since MagicDNS is enabled, you can access devices - based on their name and also at - {' '} - - [name]. - {data.magic} - - - - ) - : undefined} + > + Since MagicDNS is enabled, you can access devices based on + their name and also at{' '} + + [name]. + {data.magic} + + + + ) : undefined}
Last Seen - - {data.nodes.map(machine => ( + {data.nodes.map((machine) => ( route.node.id === machine.id)} + routes={data.routes.filter( + (route) => route.node.id === machine.id, + )} users={data.users} magic={data.magic} /> @@ -130,5 +130,5 @@ export default function Page() { - ) + ); } diff --git a/app/routes/settings/auth-keys.tsx b/app/routes/settings/auth-keys.tsx index 9470bb1..170ee6b 100644 --- a/app/routes/settings/auth-keys.tsx +++ b/app/routes/settings/auth-keys.tsx @@ -1,168 +1,174 @@ -import { LoaderFunctionArgs, ActionFunctionArgs } from '@remix-run/node' -import { useLoaderData } from '@remix-run/react' -import { useLiveData } from '~/utils/useLiveData' -import { getSession } from '~/utils/sessions' -import { Link as RemixLink } from '@remix-run/react' -import { PreAuthKey, User } from '~/types' -import { pull, post } from '~/utils/headscale' -import { loadContext } from '~/utils/config/headplane' -import { useState } from 'react' -import { send } from '~/utils/res' +import { LoaderFunctionArgs, ActionFunctionArgs } from 'react-router'; +import { useLoaderData } from 'react-router'; +import { useLiveData } from '~/utils/useLiveData'; +import { getSession } from '~/utils/sessions'; +import { Link as RemixLink } from 'react-router'; +import { PreAuthKey, User } from '~/types'; +import { pull, post } from '~/utils/headscale'; +import { loadContext } from '~/utils/config/headplane'; +import { useState } from 'react'; +import { send } from '~/utils/res'; -import Link from '~/components/Link' -import TableList from '~/components/TableList' -import Select from '~/components/Select' -import Switch from '~/components/Switch' +import Link from '~/components/Link'; +import TableList from '~/components/TableList'; +import Select from '~/components/Select'; +import Switch from '~/components/Switch'; -import AddPreAuthKey from './dialogs/new' -import AuthKeyRow from './components/key' +import AddPreAuthKey from './dialogs/new'; +import AuthKeyRow from './components/key'; export async function action({ request }: ActionFunctionArgs) { - const session = await getSession(request.headers.get('Cookie')) + const session = await getSession(request.headers.get('Cookie')); if (!session.has('hsApiKey')) { - return send({ message: 'Unauthorized' }, { - status: 401, - }) + return send( + { message: 'Unauthorized' }, + { + status: 401, + }, + ); } - const data = await request.formData() + const data = await request.formData(); // Expiring a pre-auth key if (request.method === 'DELETE') { - const key = data.get('key') - const user = data.get('user') + const key = data.get('key'); + const user = data.get('user'); if (!key || !user) { - return send({ message: 'Missing parameters' }, { - status: 400, - }) + return send( + { message: 'Missing parameters' }, + { + status: 400, + }, + ); } await post<{ preAuthKey: PreAuthKey }>( 'v1/preauthkey/expire', - session.get('hsApiKey')!, + session.get('hsApiKey')!, { user: user, key: key, - } - ) + }, + ); - return { message: 'Pre-auth key expired' } + return { message: 'Pre-auth key expired' }; } // Creating a new pre-auth key if (request.method === 'POST') { - const user = data.get('user') - const expiry = data.get('expiry') - const reusable = data.get('reusable') - const ephemeral = data.get('ephemeral') + const user = data.get('user'); + const expiry = data.get('expiry'); + const reusable = data.get('reusable'); + const ephemeral = data.get('ephemeral'); if (!user || !expiry || !reusable || !ephemeral) { - return send({ message: 'Missing parameters' }, { - status: 400, - }) + return send( + { message: 'Missing parameters' }, + { + status: 400, + }, + ); } // Extract the first "word" from expiry which is the day number // Calculate the date X days from now using the day number - const day = Number(expiry.toString().split(' ')[0]) - const date = new Date() - date.setDate(date.getDate() + day) + const day = Number(expiry.toString().split(' ')[0]); + const date = new Date(); + date.setDate(date.getDate() + day); const key = await post<{ preAuthKey: PreAuthKey }>( 'v1/preauthkey', - session.get('hsApiKey')!, + session.get('hsApiKey')!, { user: user, ephemeral: ephemeral === 'on', reusable: reusable === 'on', expiration: date.toISOString(), aclTags: [], // TODO - } - ) + }, + ); - return { message: 'Pre-auth key created', key } + return { message: 'Pre-auth key created', key }; } } export async function loader({ request }: LoaderFunctionArgs) { - const context = await loadContext() - const session = await getSession(request.headers.get('Cookie')) - const users = await pull<{ users: User[] }>('v1/user', session.get('hsApiKey')!) + const context = await loadContext(); + const session = await getSession(request.headers.get('Cookie')); + const users = await pull<{ users: User[] }>( + 'v1/user', + session.get('hsApiKey')!, + ); - const preAuthKeys = await Promise.all(users.users.map(user => { - const qp = new URLSearchParams() - qp.set('user', user.name) + const preAuthKeys = await Promise.all( + users.users.map((user) => { + const qp = new URLSearchParams(); + qp.set('user', user.name); - return pull<{ preAuthKeys: PreAuthKey[] }>( - `v1/preauthkey?${qp.toString()}`, - session.get('hsApiKey')! - ) - })) + return pull<{ preAuthKeys: PreAuthKey[] }>( + `v1/preauthkey?${qp.toString()}`, + session.get('hsApiKey')!, + ); + }), + ); return { - keys: preAuthKeys.flatMap(keys => keys.preAuthKeys), + keys: preAuthKeys.flatMap((keys) => keys.preAuthKeys), users: users.users, server: context.headscalePublicUrl ?? context.headscaleUrl, - } + }; } export default function Page() { - const { keys, users, server } = useLoaderData() - const [user, setUser] = useState('All') - const [status, setStatus] = useState('Active') - useLiveData({ interval: 3000 }) + const { keys, users, server } = useLoaderData(); + const [user, setUser] = useState('All'); + const [status, setStatus] = useState('Active'); + useLiveData({ interval: 3000 }); - const filteredKeys = keys.filter(key => { + const filteredKeys = keys.filter((key) => { if (user !== 'All' && key.user !== user) { - return false + return false; } if (status !== 'All') { - const now = new Date() - const expiry = new Date(key.expiration) + const now = new Date(); + const expiry = new Date(key.expiration); if (status === 'Active') { - return !(expiry < now) && !key.used + return !(expiry < now) && !key.used; } if (status === 'Used/Expired') { - return key.used || expiry < now + return key.used || expiry < now; } if (status === 'Reusable') { - return key.reusable + return key.reusable; } if (status === 'Ephemeral') { - return key.ephemeral + return key.ephemeral; } } - return true - }) + return true; + }); return ( -
+

- + Settings - - / - - {' '} - Pre-Auth Keys + / Pre-Auth Keys

-

Pre-Auth Keys

+

Pre-Auth Keys

- Headscale fully supports pre-authentication keys in order to - easily add devices to your Tailnet. - To learn more about using pre-authentication keys, visit the - {' '} + Headscale fully supports pre-authentication keys in order to easily add + devices to your Tailnet. To learn more about using pre-authentication + keys, visit the{' '} All - {users.map(user => ( + {users.map((user) => ( {user.name} @@ -209,16 +215,16 @@ export default function Page() { {filteredKeys.length === 0 ? ( -

- No pre-auth keys -

+

No pre-auth keys

- ) : filteredKeys.map(key => ( - - - - ))} + ) : ( + filteredKeys.map((key) => ( + + + + )) + )}
- ) + ); } diff --git a/app/routes/settings/components/key.tsx b/app/routes/settings/components/key.tsx index 0dcb2cf..a9acf2d 100644 --- a/app/routes/settings/components/key.tsx +++ b/app/routes/settings/components/key.tsx @@ -1,19 +1,19 @@ -import type { PreAuthKey } from '~/types' -import { toast } from '~/components/Toaster' +import type { PreAuthKey } from '~/types'; +import { toast } from '~/components/Toaster'; -import Code from '~/components/Code' -import Button from '~/components/Button' -import Attribute from '~/components/Attribute' -import ExpireKey from '../dialogs/expire' +import Code from '~/components/Code'; +import Button from '~/components/Button'; +import Attribute from '~/components/Attribute'; +import ExpireKey from '../dialogs/expire'; interface Props { - authKey: PreAuthKey - server: string + authKey: PreAuthKey; + server: string; } export default function AuthKeyRow({ authKey, server }: Props) { - const createdAt = new Date(authKey.createdAt).toLocaleString() - const expiration = new Date(authKey.expiration).toLocaleString() + const createdAt = new Date(authKey.createdAt).toLocaleString(); + const expiration = new Date(authKey.expiration).toLocaleString(); return (
@@ -31,25 +31,24 @@ export default function AuthKeyRow({ authKey, server }: Props) { tailscale up --login-server {server} --authkey {authKey.key}
- {authKey.used || new Date(authKey.expiration) < new Date() - ? undefined - : ( - - )} + {authKey.used || + new Date(authKey.expiration) < new Date() ? undefined : ( + + )}
- ) + ); } diff --git a/app/routes/settings/dialogs/expire.tsx b/app/routes/settings/dialogs/expire.tsx index d3f48d0..37766c0 100644 --- a/app/routes/settings/dialogs/expire.tsx +++ b/app/routes/settings/dialogs/expire.tsx @@ -1,45 +1,40 @@ -import { useFetcher } from '@remix-run/react' -import type { PreAuthKey } from '~/types' -import { cn } from '~/utils/cn' +import { useFetcher } from 'react-router'; +import type { PreAuthKey } from '~/types'; +import { cn } from '~/utils/cn'; -import Dialog from '~/components/Dialog' -import Spinner from '~/components/Spinner' +import Dialog from '~/components/Dialog'; +import Spinner from '~/components/Spinner'; interface Props { - authKey: PreAuthKey + authKey: PreAuthKey; } export default function ExpireKey({ authKey }: Props) { - const fetcher = useFetcher() + const fetcher = useFetcher(); return ( - - Expire Key - + Expire Key - {close => ( + {(close) => ( <> - - Expire auth key? - - { - fetcher.submit(e.currentTarget) - close() - }}> + Expire auth key? + { + fetcher.submit(e.currentTarget); + close(); + }} + > - Expiring this authentication key will immediately - prevent it from being used to authenticate new devices. - {' '} - This action cannot be undone. + Expiring this authentication key will immediately prevent it + from being used to authenticate new devices. This action cannot + be undone.
- + Cancel - {fetcher.state === 'idle' - ? undefined - : ( - - )} + {fetcher.state === 'idle' ? undefined : ( + + )} Expire
@@ -65,5 +58,5 @@ export default function ExpireKey({ authKey }: Props) { )}
- ) + ); } diff --git a/app/routes/settings/dialogs/new.tsx b/app/routes/settings/dialogs/new.tsx index ce2858f..c47be3f 100644 --- a/app/routes/settings/dialogs/new.tsx +++ b/app/routes/settings/dialogs/new.tsx @@ -1,50 +1,47 @@ -import { RepoForkedIcon } from '@primer/octicons-react' -import { useFetcher } from '@remix-run/react' -import { useState } from 'react' +import { RepoForkedIcon } from '@primer/octicons-react'; +import { useFetcher } from 'react-router'; +import { useState } from 'react'; -import Dialog from '~/components/Dialog' -import TextField from '~/components/TextField' -import NumberField from '~/components/NumberField' -import Tooltip from '~/components/Tooltip' -import Select from '~/components/Select' -import Switch from '~/components/Switch' -import Link from '~/components/Link' -import Spinner from '~/components/Spinner' +import Dialog from '~/components/Dialog'; +import TextField from '~/components/TextField'; +import NumberField from '~/components/NumberField'; +import Tooltip from '~/components/Tooltip'; +import Select from '~/components/Select'; +import Switch from '~/components/Switch'; +import Link from '~/components/Link'; +import Spinner from '~/components/Spinner'; -import { cn } from '~/utils/cn' -import { User } from '~/types' +import { cn } from '~/utils/cn'; +import { User } from '~/types'; interface Props { - users: User[] + users: User[]; } // TODO: Tags export default function AddPreAuthKey(data: Props) { - const fetcher = useFetcher() - const [user, setUser] = useState('') - const [reusable, setReusable] = useState(false) - const [ephemeral, setEphemeral] = useState(false) - const [aclTags, setAclTags] = useState([]) - const [expiry, setExpiry] = useState(90) + const fetcher = useFetcher(); + const [user, setUser] = useState(''); + const [reusable, setReusable] = useState(false); + const [ephemeral, setEphemeral] = useState(false); + const [aclTags, setAclTags] = useState([]); + const [expiry, setExpiry] = useState(90); return ( - - Create pre-auth key - + Create pre-auth key - {close => ( + {(close) => ( <> - - Generate auth key - - { - fetcher.submit(e.currentTarget) - close() - }}> - - User - + Generate auth key + { + fetcher.submit(e.currentTarget); + close(); + }} + > + User Attach this key to a user @@ -54,7 +51,7 @@ export default function AddPreAuthKey(data: Props) { placeholder="Select a user" state={[user, setUser]} > - {data.users.map(user => ( + {data.users.map((user) => ( {user.name} @@ -80,9 +77,7 @@ export default function AddPreAuthKey(data: Props) { />
- - Reusable - + Reusable Use this key to authenticate more than one device. @@ -91,19 +86,22 @@ export default function AddPreAuthKey(data: Props) { label="Reusable" name="reusable" defaultSelected={reusable} - onChange={() => { setReusable(!reusable) }} + onChange={() => { + setReusable(!reusable); + }} />
- +
- - Ephemeral - + Ephemeral - Devices authenticated with this key will - be automatically removed once they go offline. - {' '} + Devices authenticated with this key will be automatically + removed once they go offline.{' '} { - setEphemeral(!ephemeral) + setEphemeral(!ephemeral); }} />
- +
- + Cancel - {fetcher.state === 'idle' - ? undefined - : ( - - )} + {fetcher.state === 'idle' ? undefined : ( + + )} Generate
@@ -147,5 +144,5 @@ export default function AddPreAuthKey(data: Props) { )}
- ) + ); } diff --git a/app/routes/settings/overview.tsx b/app/routes/settings/overview.tsx index 6a4e825..33f5757 100644 --- a/app/routes/settings/overview.tsx +++ b/app/routes/settings/overview.tsx @@ -1,28 +1,26 @@ -import Link from '~/components/Link' -import Button from '~/components/Button' -import { Link as RemixLink } from '@remix-run/react' -import { ArrowRightIcon } from '@primer/octicons-react' -import { cn } from '~/utils/cn' +import Link from '~/components/Link'; +import Button from '~/components/Button'; +import { Link as RemixLink } from 'react-router'; +import { ArrowRightIcon } from '@primer/octicons-react'; +import { cn } from '~/utils/cn'; export default function Page() { return (
-
-

Settings

+
+

Settings

- The settings page is still under construction. - As I'm able to add more features, I'll be adding them here. - If you require any features, feel free to open an issue on - the GitHub repository. + The settings page is still under construction. As I'm able to add more + features, I'll be adding them here. If you require any features, feel + free to open an issue on the GitHub repository.

-
-

Pre-Auth Keys

+
+

Pre-Auth Keys

- Headscale fully supports pre-authentication keys in order to - easily add devices to your Tailnet. - To learn more about using pre-authentication keys, visit the - {' '} + Headscale fully supports pre-authentication keys in order to easily + add devices to your Tailnet. To learn more about using + pre-authentication keys, visit the{' '}

- + Manage Auth Keys
- ) + ); } diff --git a/app/routes/users/components/auth.tsx b/app/routes/users/components/auth.tsx index 3cd4620..9d16400 100644 --- a/app/routes/users/components/auth.tsx +++ b/app/routes/users/components/auth.tsx @@ -1,12 +1,12 @@ -import { HomeIcon, PasskeyFillIcon } from '@primer/octicons-react' +import { HomeIcon, PasskeyFillIcon } from '@primer/octicons-react'; -import Card from '~/components/Card' -import Link from '~/components/Link' +import Card from '~/components/Card'; +import Link from '~/components/Link'; -import Add from '../dialogs/add' +import Add from '../dialogs/add'; interface Props { - readonly magic: string | undefined + readonly magic: string | undefined; } export default function Auth({ magic }: Props) { @@ -15,14 +15,10 @@ export default function Auth({ magic }: Props) {
-

- Basic Authentication -

+

Basic Authentication

- Users are not managed externally. - Using OpenID Connect can create a better - experience when using Headscale. - {' '} + Users are not managed externally. Using OpenID Connect can create a + better experience when using Headscale.{' '}

-

- User Management -

+

User Management

You can add, remove, and rename users here.

@@ -45,5 +39,5 @@ export default function Auth({ magic }: Props) {
- ) + ); } diff --git a/app/routes/users/components/oidc.tsx b/app/routes/users/components/oidc.tsx index 4ae3644..8f3f200 100644 --- a/app/routes/users/components/oidc.tsx +++ b/app/routes/users/components/oidc.tsx @@ -1,14 +1,14 @@ -import { OrganizationIcon, PasskeyFillIcon } from '@primer/octicons-react' +import { OrganizationIcon, PasskeyFillIcon } from '@primer/octicons-react'; -import Card from '~/components/Card' -import Link from '~/components/Link' -import { HeadplaneContext } from '~/utils/config/headplane' +import Card from '~/components/Card'; +import Link from '~/components/Link'; +import { HeadplaneContext } from '~/utils/config/headplane'; -import Add from '../dialogs/add' +import Add from '../dialogs/add'; interface Props { - readonly oidc: NonNullable - readonly magic: string | undefined + readonly oidc: NonNullable; + readonly magic: string | undefined; } export default function Oidc({ oidc, magic }: Props) { @@ -17,18 +17,14 @@ export default function Oidc({ oidc, magic }: Props) {
-

- OpenID Connect -

+

OpenID Connect

- Users are managed through your - {' '} + Users are managed through your{' '} OpenID Connect provider {'. '} - Groups and user information do not automatically sync. - {' '} + Groups and user information do not automatically sync.{' '}

-

- User Management -

+

User Management

- You can still add users manually, however it is recommended - that you manage users through your OIDC provider. + You can still add users manually, however it is recommended that you + manage users through your OIDC provider.

@@ -52,5 +46,5 @@ export default function Oidc({ oidc, magic }: Props) {
- ) + ); } diff --git a/app/routes/users/dialogs/add.tsx b/app/routes/users/dialogs/add.tsx index d693e3d..5bdabe7 100644 --- a/app/routes/users/dialogs/add.tsx +++ b/app/routes/users/dialogs/add.tsx @@ -1,53 +1,39 @@ -import { Form, useSubmit } from '@remix-run/react' -import { useState } from 'react' +import { Form, useSubmit } from 'react-router'; +import { useState } from 'react'; -import Code from '~/components/Code' -import Dialog from '~/components/Dialog' -import TextField from '~/components/TextField' +import Code from '~/components/Code'; +import Dialog from '~/components/Dialog'; +import TextField from '~/components/TextField'; interface Props { - magic?: string + magic?: string; } export default function Add({ magic }: Props) { - const [username, setUsername] = useState('') - const submit = useSubmit() + const [username, setUsername] = useState(''); + const submit = useSubmit(); return ( - - Add a new user - + Add a new user - {close => ( + {(close) => ( <> - - Add a new user - + Add a new user - Enter a username to create a new user. - {' '} - {magic - ? ( - <> - Since Magic DNS is enabled, machines will be - accessible via - {' '} - - [machine]. - . - {magic} - - . - - ) - : undefined} + Enter a username to create a new user.{' '} + {magic ? ( + <> + Since Magic DNS is enabled, machines will be accessible via{' '} + [machine]. .{magic}. + + ) : undefined} { - submit(event.currentTarget) + submit(event.currentTarget); }} > @@ -59,16 +45,10 @@ export default function Add({ magic }: Props) { className="my-2" />
- + Cancel - + Create
@@ -77,5 +57,5 @@ export default function Add({ magic }: Props) { )}
- ) + ); } diff --git a/app/routes/users/dialogs/remove.tsx b/app/routes/users/dialogs/remove.tsx index 6d87c6e..735f90a 100644 --- a/app/routes/users/dialogs/remove.tsx +++ b/app/routes/users/dialogs/remove.tsx @@ -1,18 +1,18 @@ -import { XIcon } from '@primer/octicons-react' -import { Form, useSubmit } from '@remix-run/react' -import { useState } from 'react' +import { XIcon } from '@primer/octicons-react'; +import { Form, useSubmit } from 'react-router'; +import { useState } from 'react'; -import Button from '~/components/Button' -import Code from '~/components/Code' -import Dialog from '~/components/Dialog' +import Button from '~/components/Button'; +import Code from '~/components/Code'; +import Dialog from '~/components/Dialog'; interface Props { - username: string + username: string; } export default function Remove({ username }: Props) { - const submit = useSubmit() - const dialogState = useState(false) + const submit = useSubmit(); + const dialogState = useState(false); return ( <> @@ -25,41 +25,26 @@ export default function Remove({ username }: Props) { - {close => ( + {(close) => ( <> - - Delete - {' '} - {username} - ? - + Delete {username}? - Are you sure you want to delete - {' '} - {username} - ? - {' '} - A deleted user cannot be recovered. + Are you sure you want to delete {username}? A deleted user + cannot be recovered. { - submit(event.currentTarget) + submit(event.currentTarget); }} >
- + Cancel - + Delete
@@ -69,5 +54,5 @@ export default function Remove({ username }: Props) {
- ) + ); } diff --git a/app/routes/users/dialogs/rename.tsx b/app/routes/users/dialogs/rename.tsx index c08a624..327c90c 100644 --- a/app/routes/users/dialogs/rename.tsx +++ b/app/routes/users/dialogs/rename.tsx @@ -1,20 +1,20 @@ -import { PencilIcon } from '@primer/octicons-react' -import { Form, useSubmit } from '@remix-run/react' -import { useState } from 'react' +import { PencilIcon } from '@primer/octicons-react'; +import { Form, useSubmit } from 'react-router'; +import { useState } from 'react'; -import Button from '~/components/Button' -import Dialog from '~/components/Dialog' -import TextField from '~/components/TextField' +import Button from '~/components/Button'; +import Dialog from '~/components/Dialog'; +import TextField from '~/components/TextField'; interface Props { - username: string - magic?: string + username: string; + magic?: string; } export default function Rename({ username, magic }: Props) { - const submit = useSubmit() - const dialogState = useState(false) - const [newName, setNewName] = useState(username) + const submit = useSubmit(); + const dialogState = useState(false); + const [newName, setNewName] = useState(username); return ( <> @@ -27,23 +27,16 @@ export default function Rename({ username, magic }: Props) { - {close => ( + {(close) => ( <> - - Rename - {' '} - {username} - ? - + Rename {username}? - Enter a new username for - {' '} - {username} + Enter a new username for {username} { - submit(event.currentTarget) + submit(event.currentTarget); }} > @@ -56,16 +49,10 @@ export default function Rename({ username, magic }: Props) { className="my-2" />
- + Cancel - + Rename
@@ -75,5 +62,5 @@ export default function Rename({ username, magic }: Props) {
- ) + ); } diff --git a/app/routes/users/overview.tsx b/app/routes/users/overview.tsx index 237e2e5..4c37d32 100644 --- a/app/routes/users/overview.tsx +++ b/app/routes/users/overview.tsx @@ -1,48 +1,48 @@ -import { DataRef, DndContext, useDraggable, useDroppable } from '@dnd-kit/core' -import { PersonIcon } from '@primer/octicons-react' -import { ActionFunctionArgs, LoaderFunctionArgs } from '@remix-run/node' -import { useActionData, useLoaderData, useSubmit } from '@remix-run/react' -import { useEffect, useState } from 'react' -import { ClientOnly } from 'remix-utils/client-only' +import { DataRef, DndContext, useDraggable, useDroppable } from '@dnd-kit/core'; +import { PersonIcon } from '@primer/octicons-react'; +import { ActionFunctionArgs, LoaderFunctionArgs } from 'react-router'; +import { useActionData, useLoaderData, useSubmit } from 'react-router'; +import { useEffect, useState } from 'react'; +import { ClientOnly } from 'remix-utils/client-only'; -import Attribute from '~/components/Attribute' -import Card from '~/components/Card' -import StatusCircle from '~/components/StatusCircle' -import { toast } from '~/components/Toaster' -import { Machine, User } from '~/types' -import { cn } from '~/utils/cn' -import { loadContext } from '~/utils/config/headplane' -import { loadConfig } from '~/utils/config/headscale' -import { del, post, pull } from '~/utils/headscale' -import { getSession } from '~/utils/sessions' -import { useLiveData } from '~/utils/useLiveData' -import { send } from '~/utils/res' +import Attribute from '~/components/Attribute'; +import Card from '~/components/Card'; +import StatusCircle from '~/components/StatusCircle'; +import { toast } from '~/components/Toaster'; +import { Machine, User } from '~/types'; +import { cn } from '~/utils/cn'; +import { loadContext } from '~/utils/config/headplane'; +import { loadConfig } from '~/utils/config/headscale'; +import { del, post, pull } from '~/utils/headscale'; +import { getSession } from '~/utils/sessions'; +import { useLiveData } from '~/utils/useLiveData'; +import { send } from '~/utils/res'; -import Auth from './components/auth' -import Oidc from './components/oidc' -import Remove from './dialogs/remove' -import Rename from './dialogs/rename' +import Auth from './components/auth'; +import Oidc from './components/oidc'; +import Remove from './dialogs/remove'; +import Rename from './dialogs/rename'; export async function loader({ request }: LoaderFunctionArgs) { - const session = await getSession(request.headers.get('Cookie')) + const session = await getSession(request.headers.get('Cookie')); const [machines, apiUsers] = await Promise.all([ pull<{ nodes: Machine[] }>('v1/node', session.get('hsApiKey')!), pull<{ users: User[] }>('v1/user', session.get('hsApiKey')!), - ]) + ]); - const users = apiUsers.users.map(user => ({ + const users = apiUsers.users.map((user) => ({ ...user, - machines: machines.nodes.filter(machine => machine.user.id === user.id), - })) + machines: machines.nodes.filter((machine) => machine.user.id === user.id), + })); - const context = await loadContext() - let magic: string | undefined + const context = await loadContext(); + let magic: string | undefined; if (context.config.read) { - const config = await loadConfig() + const config = await loadConfig(); if (config.dns.magic_dns) { - magic = config.dns.base_domain + magic = config.dns.base_domain; } } @@ -50,125 +50,115 @@ export async function loader({ request }: LoaderFunctionArgs) { oidc: context.oidc, magic, users, - } + }; } export async function action({ request }: ActionFunctionArgs) { - const session = await getSession(request.headers.get('Cookie')) + const session = await getSession(request.headers.get('Cookie')); if (!session.has('hsApiKey')) { - return send({ message: 'Unauthorized' }, 401) + return send({ message: 'Unauthorized' }, 401); } - const data = await request.formData() + const data = await request.formData(); if (!data.has('_method')) { - return send({ message: 'No method provided' }, 400) + return send({ message: 'No method provided' }, 400); } - const method = String(data.get('_method')) + const method = String(data.get('_method')); switch (method) { case 'create': { if (!data.has('username')) { - return send({ message: 'No name provided' }, 400) + return send({ message: 'No name provided' }, 400); } - const username = String(data.get('username')) + const username = String(data.get('username')); await post('v1/user', session.get('hsApiKey')!, { name: username, - }) + }); - return { message: `User ${username} created` } + return { message: `User ${username} created` }; } case 'delete': { if (!data.has('username')) { - return send({ message: 'No name provided' }, 400) + return send({ message: 'No name provided' }, 400); } - const username = String(data.get('username')) - await del(`v1/user/${username}`, session.get('hsApiKey')!) - return { message: `User ${username} deleted` } + const username = String(data.get('username')); + await del(`v1/user/${username}`, session.get('hsApiKey')!); + return { message: `User ${username} deleted` }; } case 'rename': { if (!data.has('old') || !data.has('new')) { - return send({ message: 'No old or new name provided' }, 400) + return send({ message: 'No old or new name provided' }, 400); } - const old = String(data.get('old')) - const newName = String(data.get('new')) - await post(`v1/user/${old}/rename/${newName}`, session.get('hsApiKey')!) - return { message: `User ${old} renamed to ${newName}` } + const old = String(data.get('old')); + const newName = String(data.get('new')); + await post(`v1/user/${old}/rename/${newName}`, session.get('hsApiKey')!); + return { message: `User ${old} renamed to ${newName}` }; } case 'move': { if (!data.has('id') || !data.has('to') || !data.has('name')) { - return send({ message: 'No ID or destination provided' }, 400) + return send({ message: 'No ID or destination provided' }, 400); } - const id = String(data.get('id')) - const to = String(data.get('to')) - const name = String(data.get('name')) + const id = String(data.get('id')); + const to = String(data.get('to')); + const name = String(data.get('name')); try { - await post(`v1/node/${id}/user?user=${to}`, session.get('hsApiKey')!) - return { message: `Moved ${name} to ${to}` } + await post(`v1/node/${id}/user?user=${to}`, session.get('hsApiKey')!); + return { message: `Moved ${name} to ${to}` }; } catch { - return send({ message: `Failed to move ${name} to ${to}` }, 500) + return send({ message: `Failed to move ${name} to ${to}` }, 500); } } default: { - return send({ message: 'Invalid method' }, 400) + return send({ message: 'Invalid method' }, 400); } } } export default function Page() { - const data = useLoaderData() - const [users, setUsers] = useState(data.users) - const actionData = useActionData() - useLiveData({ interval: 3000 }) + const data = useLoaderData(); + const [users, setUsers] = useState(data.users); + const actionData = useActionData(); + useLiveData({ interval: 3000 }); useEffect(() => { if (!actionData) { - return + return; } - toast(actionData.message) + toast(actionData.message); if (actionData.message.startsWith('Failed')) { - setUsers(data.users) + setUsers(data.users); } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [actionData]) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [actionData]); useEffect(() => { - setUsers(data.users) - }, [data.users]) + setUsers(data.users); + }, [data.users]); return ( <> -

- Users -

+

Users

- Manage the users in your network and their permissions. - Tip: You can drag machines between users to change ownership. + Manage the users in your network and their permissions. Tip: You can + drag machines between users to change ownership.

- {data.oidc - ? ( - - ) - : ( - - )} - - } - > + {data.oidc ? ( + + ) : ( + + )} + }> {() => ( - ) + ); } -type UserMachine = User & { machines: Machine[] } +type UserMachine = User & { machines: Machine[] }; interface UserProps { - users: UserMachine[] - setUsers?: (users: UserMachine[]) => void - magic?: string + users: UserMachine[]; + setUsers?: (users: UserMachine[]) => void; + magic?: string; } function Users({ users, magic }: UserProps) { return (
- {users.map(user => ( - + {users.map((user) => ( + ))}
- ) + ); } function InteractiveUsers({ users, setUsers, magic }: UserProps) { - const submit = useSubmit() + const submit = useSubmit(); return ( - { - const { over, active } = event - if (!over) { - return - } + { + const { over, active } = event; + if (!over) { + return; + } - // Update the UI optimistically - const newUsers = new Array() - const reference = active.data as DataRef - if (!reference.current) { - return - } + // Update the UI optimistically + const newUsers = new Array(); + const reference = active.data as DataRef; + if (!reference.current) { + return; + } - // Ignore if the user is unchanged - if (reference.current.user.name === over.id) { - return - } + // Ignore if the user is unchanged + if (reference.current.user.name === over.id) { + return; + } - for (const user of users) { - newUsers.push({ - ...user, - machines: over.id === user.name - ? [...user.machines, reference.current] - : user.machines.filter(m => m.id !== active.id), - }) - } + for (const user of users) { + newUsers.push({ + ...user, + machines: + over.id === user.name + ? [...user.machines, reference.current] + : user.machines.filter((m) => m.id !== active.id), + }); + } - setUsers?.(newUsers) - const data = new FormData() - data.append('_method', 'move') - data.append('id', active.id.toString()) - data.append('to', over.id.toString()) - data.append('name', reference.current.givenName) + setUsers?.(newUsers); + const data = new FormData(); + data.append('_method', 'move'); + data.append('id', active.id.toString()); + data.append('to', over.id.toString()); + data.append('name', reference.current.givenName); - submit(data, { - method: 'POST', - }) - }} + submit(data, { + method: 'POST', + }); + }} >
- {users.map(user => ( - + {users.map((user) => ( + ))}
- ) + ); } function MachineChip({ machine }: { readonly machine: Machine }) { const { attributes, listeners, setNodeRef, transform } = useDraggable({ id: machine.id, data: machine, - }) + }); return (
- ) + ); } interface CardProps { - user: UserMachine - magic?: string + user: UserMachine; + magic?: string; } function UserCard({ user, magic }: CardProps) { const { isOver, setNodeRef } = useDroppable({ id: user.name, - }) + }); return (
@@ -312,25 +296,21 @@ function UserCard({ user, magic }: CardProps) {
- - {user.name} - + {user.name}
- {user.machines.length === 0 - ? ( - - ) - : undefined} + {user.machines.length === 0 ? ( + + ) : undefined}
- {user.machines.map(machine => ( + {user.machines.map((machine) => ( ))}
- ) + ); } diff --git a/app/routes/util/healthz.ts b/app/routes/util/healthz.ts index 0e80103..d7f6b14 100644 --- a/app/routes/util/healthz.ts +++ b/app/routes/util/healthz.ts @@ -1,23 +1,26 @@ -import { loadContext } from '~/utils/config/headplane' -import { HeadscaleError, pull } from '~/utils/headscale' -import { data } from '@remix-run/node' -import log from '~/utils/log' +import { loadContext } from '~/utils/config/headplane'; +import { HeadscaleError, pull } from '~/utils/headscale'; +import { data } from 'react-router'; +import log from '~/utils/log'; export async function loader() { - const context = await loadContext() + const context = await loadContext(); try { // Doesn't matter, we just need a 401 - await pull('v1/', 'wrongkey') + await pull('v1/', 'wrongkey'); } catch (e) { if (!(e instanceof HeadscaleError)) { - log.debug('Healthz', 'Headscale is not reachable') - return data({ - status: 'NOT OK', - error: e.message - }, { status: 500 }) + log.debug('Healthz', 'Headscale is not reachable'); + return data( + { + status: 'NOT OK', + error: e.message, + }, + { status: 500 }, + ); } } - return { status: 'OK' } + return { status: 'OK' }; } diff --git a/app/routes/util/redirect.ts b/app/routes/util/redirect.ts index 9f3f621..beb7acc 100644 --- a/app/routes/util/redirect.ts +++ b/app/routes/util/redirect.ts @@ -1,5 +1,5 @@ -import { redirect } from '@remix-run/node' +import { redirect } from 'react-router'; export async function loader() { - return redirect('/machines') + return redirect('/machines'); } diff --git a/app/tailwind.css b/app/tailwind.css index f2e8171..47c0f22 100644 --- a/app/tailwind.css +++ b/app/tailwind.css @@ -3,9 +3,9 @@ @tailwind utilities; @supports (scrollbar-gutter: stable) { - html { - scrollbar-gutter: stable - } + html { + scrollbar-gutter: stable; + } } .cm-merge-theme { @@ -25,6 +25,7 @@ } /* Weirdest class name characters but ok */ -.cm-mergeView .ͼ1 .cm-scroller, .cm-mergeView .ͼ1 { +.cm-mergeView .ͼ1 .cm-scroller, +.cm-mergeView .ͼ1 { height: 100% !important; } diff --git a/app/types/Key.ts b/app/types/Key.ts index d496d01..0b41a4d 100644 --- a/app/types/Key.ts +++ b/app/types/Key.ts @@ -4,4 +4,4 @@ export type Key = { expiration: string; createdAt: Date; lastSeen: Date; -} +}; diff --git a/app/types/Machine.ts b/app/types/Machine.ts index 5dc17aa..6fbee75 100644 --- a/app/types/Machine.ts +++ b/app/types/Machine.ts @@ -1,28 +1,29 @@ -import type { User } from './User' +import type { User } from './User'; export interface Machine { - id: string - machineKey: string - nodeKey: string - discoKey: string - ipAddresses: string[] - name: string + id: string; + machineKey: string; + nodeKey: string; + discoKey: string; + ipAddresses: string[]; + name: string; - user: User - lastSeen: string - expiry: string + user: User; + lastSeen: string; + expiry: string; - preAuthKey?: unknown // TODO + preAuthKey?: unknown; // TODO - createdAt: string - registerMethod: 'REGISTER_METHOD_UNSPECIFIED' - | 'REGISTER_METHOD_AUTH_KEY' - | 'REGISTER_METHOD_CLI' - | 'REGISTER_METHOD_OIDC' + createdAt: string; + registerMethod: + | 'REGISTER_METHOD_UNSPECIFIED' + | 'REGISTER_METHOD_AUTH_KEY' + | 'REGISTER_METHOD_CLI' + | 'REGISTER_METHOD_OIDC'; - forcedTags: string[] - invalidTags: string[] - validTags: string[] - givenName: string - online: boolean + forcedTags: string[]; + invalidTags: string[]; + validTags: string[]; + givenName: string; + online: boolean; } diff --git a/app/types/PreAuthKey.ts b/app/types/PreAuthKey.ts index 04c456d..1b6e9b7 100644 --- a/app/types/PreAuthKey.ts +++ b/app/types/PreAuthKey.ts @@ -1,11 +1,11 @@ export interface PreAuthKey { - id: string - key: string - user: string - reusable: boolean - ephemeral: boolean - used: boolean - expiration: string - createdAt: string - aclTags: string[] + id: string; + key: string; + user: string; + reusable: boolean; + ephemeral: boolean; + used: boolean; + expiration: string; + createdAt: string; + aclTags: string[]; } diff --git a/app/types/Route.ts b/app/types/Route.ts index fa1c03b..6fdcb55 100644 --- a/app/types/Route.ts +++ b/app/types/Route.ts @@ -1,13 +1,13 @@ -import type { Machine } from './Machine' +import type { Machine } from './Machine'; export interface Route { - id: string - node: Machine - prefix: string - advertised: boolean - enabled: boolean - isPrimary: boolean - createdAt: string - updatedAt: string - deletedAt: string + id: string; + node: Machine; + prefix: string; + advertised: boolean; + enabled: boolean; + isPrimary: boolean; + createdAt: string; + updatedAt: string; + deletedAt: string; } diff --git a/app/types/User.ts b/app/types/User.ts index b8e3495..c7cb7c4 100644 --- a/app/types/User.ts +++ b/app/types/User.ts @@ -1,5 +1,5 @@ export interface User { - id: string - name: string - createdAt: string + id: string; + name: string; + createdAt: string; } diff --git a/app/types/index.ts b/app/types/index.ts index 1bd9b93..c2b3405 100644 --- a/app/types/index.ts +++ b/app/types/index.ts @@ -1,5 +1,5 @@ -export * from './Key' -export * from './Machine' -export * from './Route' -export * from './User' -export * from './PreAuthKey' +export * from './Key'; +export * from './Machine'; +export * from './Route'; +export * from './User'; +export * from './PreAuthKey'; diff --git a/app/utils/cn.ts b/app/utils/cn.ts index 6f1805e..91bbff2 100644 --- a/app/utils/cn.ts +++ b/app/utils/cn.ts @@ -1,4 +1,4 @@ -import { type ClassValue, clsx } from 'clsx' -import { twMerge } from 'tailwind-merge' +import { type ClassValue, clsx } from 'clsx'; +import { twMerge } from 'tailwind-merge'; -export const cn = (...inputs: ClassValue[]) => twMerge(clsx(inputs)) +export const cn = (...inputs: ClassValue[]) => twMerge(clsx(inputs)); diff --git a/app/utils/config/headplane.ts b/app/utils/config/headplane.ts index 20db854..f50938e 100644 --- a/app/utils/config/headplane.ts +++ b/app/utils/config/headplane.ts @@ -3,82 +3,82 @@ // // Around the codebase, this is referred to as the context -import { access, constants, readFile, writeFile } from 'node:fs/promises' -import { resolve } from 'node:path' +import { access, constants, readFile, writeFile } from 'node:fs/promises'; +import { resolve } from 'node:path'; -import { parse } from 'yaml' +import { parse } from 'yaml'; -import { IntegrationFactory, loadIntegration } from '~/integration' -import { HeadscaleConfig, loadConfig } from '~/utils/config/headscale' -import { testOidc } from '~/utils/oidc' -import log from '~/utils/log' +import { IntegrationFactory, loadIntegration } from '~/integration'; +import { HeadscaleConfig, loadConfig } from '~/utils/config/headscale'; +import { testOidc } from '~/utils/oidc'; +import log from '~/utils/log'; export interface HeadplaneContext { - debug: boolean - headscaleUrl: string - headscalePublicUrl?: string - cookieSecret: string - integration: IntegrationFactory | undefined + debug: boolean; + headscaleUrl: string; + headscalePublicUrl?: string; + cookieSecret: string; + integration: IntegrationFactory | undefined; config: { - read: boolean - write: boolean - } + read: boolean; + write: boolean; + }; oidc?: { - issuer: string - client: string - secret: string - rootKey: string - method: string - disableKeyLogin: boolean - } + issuer: string; + client: string; + secret: string; + rootKey: string; + method: string; + disableKeyLogin: boolean; + }; } -let context: HeadplaneContext | undefined +let context: HeadplaneContext | undefined; export async function loadContext(): Promise { if (context) { - return context + return context; } - const envFile = process.env.LOAD_ENV_FILE === 'true' + const envFile = process.env.LOAD_ENV_FILE === 'true'; if (envFile) { - log.info('CTXT', 'Loading environment variables from .env') - await import('dotenv/config') + log.info('CTXT', 'Loading environment variables from .env'); + await import('dotenv/config'); } - const debug = process.env.DEBUG === 'true' + const debug = process.env.DEBUG === 'true'; if (debug) { - log.info('CTXT', 'Debug mode is enabled! Logs will spam a lot.') - log.info('CTXT', 'Please disable debug mode in production.') + log.info('CTXT', 'Debug mode is enabled! Logs will spam a lot.'); + log.info('CTXT', 'Please disable debug mode in production.'); } - const path = resolve(process.env.CONFIG_FILE ?? '/etc/headscale/config.yaml') - const { config, contextData } = await checkConfig(path) + const path = resolve(process.env.CONFIG_FILE ?? '/etc/headscale/config.yaml'); + const { config, contextData } = await checkConfig(path); - let headscaleUrl = process.env.HEADSCALE_URL - let headscalePublicUrl = process.env.HEADSCALE_PUBLIC_URL + let headscaleUrl = process.env.HEADSCALE_URL; + let headscalePublicUrl = process.env.HEADSCALE_PUBLIC_URL; if (!headscaleUrl && !config) { - throw new Error('HEADSCALE_URL not set') + throw new Error('HEADSCALE_URL not set'); } if (config) { - headscaleUrl = headscaleUrl ?? config.server_url + headscaleUrl = headscaleUrl ?? config.server_url; if (!headscalePublicUrl) { // Fallback to the config value if the env var is not set - headscalePublicUrl = config.public_url + headscalePublicUrl = config.public_url; } } if (!headscaleUrl) { - throw new Error('Missing server_url in headscale config') + throw new Error('Missing server_url in headscale config'); } - const cookieSecret = process.env.COOKIE_SECRET + const cookieSecret = process.env.COOKIE_SECRET; if (!cookieSecret) { - throw new Error('COOKIE_SECRET not set') + throw new Error('COOKIE_SECRET not set'); } context = { @@ -89,48 +89,51 @@ export async function loadContext(): Promise { integration: await loadIntegration(), config: contextData, oidc: await checkOidc(config), - } + }; - log.info('CTXT', 'Starting Headplane with Context') - log.info('CTXT', 'HEADSCALE_URL: %s', headscaleUrl) + log.info('CTXT', 'Starting Headplane with Context'); + log.info('CTXT', 'HEADSCALE_URL: %s', headscaleUrl); if (headscalePublicUrl) { - log.info('CTXT', 'HEADSCALE_PUBLIC_URL: %s', headscalePublicUrl) + log.info('CTXT', 'HEADSCALE_PUBLIC_URL: %s', headscalePublicUrl); } - log.info('CTXT', 'Integration: %s', context.integration?.name ?? 'None') - log.info('CTXT', 'Config: %s', contextData.read - ? `Found ${contextData.write ? '' : '(Read Only)'}` - : 'Unavailable', - ) + log.info('CTXT', 'Integration: %s', context.integration?.name ?? 'None'); + log.info( + 'CTXT', + 'Config: %s', + contextData.read + ? `Found ${contextData.write ? '' : '(Read Only)'}` + : 'Unavailable', + ); - log.info('CTXT', 'OIDC: %s', context.oidc ? 'Configured' : 'Unavailable') - return context + log.info('CTXT', 'OIDC: %s', context.oidc ? 'Configured' : 'Unavailable'); + return context; } async function checkConfig(path: string) { - log.debug('CTXT', 'Checking config at %s', path) + log.debug('CTXT', 'Checking config at %s', path); - let config: HeadscaleConfig | undefined + let config: HeadscaleConfig | undefined; try { - config = await loadConfig(path) + config = await loadConfig(path); } catch { - log.debug('CTXT', 'Config at %s failed to load', path) + log.debug('CTXT', 'Config at %s failed to load', path); return { config: undefined, contextData: { read: false, write: false, }, - } + }; } - let write = false + let write = false; try { - log.debug('CTXT', 'Checking write access to %s', path) - await access(path, constants.W_OK) - write = true + log.debug('CTXT', 'Checking write access to %s', path); + await access(path, constants.W_OK); + write = true; } catch { - log.debug('CTXT', 'No write access to %s', path) + log.debug('CTXT', 'No write access to %s', path); } return { @@ -139,49 +142,52 @@ async function checkConfig(path: string) { read: true, write, }, - } + }; } async function checkOidc(config?: HeadscaleConfig) { - log.debug('CTXT', 'Checking OIDC configuration') + log.debug('CTXT', 'Checking OIDC configuration'); - const disableKeyLogin = process.env.DISABLE_API_KEY_LOGIN === 'true' - log.debug('CTXT', 'API Key Login Enabled: %s', !disableKeyLogin) + const disableKeyLogin = process.env.DISABLE_API_KEY_LOGIN === 'true'; + log.debug('CTXT', 'API Key Login Enabled: %s', !disableKeyLogin); - log.debug('CTXT', 'Checking ROOT_API_KEY and falling back to API_KEY') - const rootKey = process.env.ROOT_API_KEY ?? process.env.API_KEY + log.debug('CTXT', 'Checking ROOT_API_KEY and falling back to API_KEY'); + const rootKey = process.env.ROOT_API_KEY ?? process.env.API_KEY; if (!rootKey) { - throw new Error('ROOT_API_KEY or API_KEY not set') + throw new Error('ROOT_API_KEY or API_KEY not set'); } - let issuer = process.env.OIDC_ISSUER - let client = process.env.OIDC_CLIENT_ID - let secret = process.env.OIDC_CLIENT_SECRET - let method = process.env.OIDC_CLIENT_SECRET_METHOD ?? 'client_secret_basic' - let skip = process.env.OIDC_SKIP_CONFIG_VALIDATION === 'true' + let issuer = process.env.OIDC_ISSUER; + let client = process.env.OIDC_CLIENT_ID; + let secret = process.env.OIDC_CLIENT_SECRET; + let method = process.env.OIDC_CLIENT_SECRET_METHOD ?? 'client_secret_basic'; + let skip = process.env.OIDC_SKIP_CONFIG_VALIDATION === 'true'; - log.debug('CTXT', 'Checking OIDC environment variables') - log.debug('CTXT', 'Issuer: %s', issuer) - log.debug('CTXT', 'Client: %s', client) + log.debug('CTXT', 'Checking OIDC environment variables'); + log.debug('CTXT', 'Issuer: %s', issuer); + log.debug('CTXT', 'Client: %s', client); if ( - (issuer ?? client ?? secret) - && !(issuer && client && secret) - && !config + (issuer ?? client ?? secret) && + !(issuer && client && secret) && + !config ) { - throw new Error('OIDC environment variables are incomplete') + throw new Error('OIDC environment variables are incomplete'); } if (issuer && client && secret) { if (!skip) { - log.debug('CTXT', 'Validating OIDC configuration from environment variables') - const result = await testOidc(issuer, client, secret) + log.debug( + 'CTXT', + 'Validating OIDC configuration from environment variables', + ); + const result = await testOidc(issuer, client, secret); if (!result) { - return + return; } } else { - log.debug('CTXT', 'OIDC_SKIP_CONFIG_VALIDATION is set') - log.debug('CTXT', 'Skipping OIDC configuration validation') + log.debug('CTXT', 'OIDC_SKIP_CONFIG_VALIDATION is set'); + log.debug('CTXT', 'Skipping OIDC configuration validation'); } return { @@ -191,51 +197,53 @@ async function checkOidc(config?: HeadscaleConfig) { method, rootKey, disableKeyLogin, - } + }; } if ((!issuer || !client || !secret) && config) { - issuer = config.oidc?.issuer - client = config.oidc?.client_id - secret = config.oidc?.client_secret + issuer = config.oidc?.issuer; + client = config.oidc?.client_id; + secret = config.oidc?.client_secret; if (!secret && config.oidc?.client_secret_path) { - log.debug('CTXT', 'Trying to read OIDC client secret from %s', config.oidc.client_secret_path) + log.debug( + 'CTXT', + 'Trying to read OIDC client secret from %s', + config.oidc.client_secret_path, + ); try { - const data = await readFile( - config.oidc.client_secret_path, - 'utf8', - ) + const data = await readFile(config.oidc.client_secret_path, 'utf8'); if (data && data.length > 0) { - secret = data.trim() + secret = data.trim(); } } catch { - log.error('CTXT', 'Failed to read OIDC client secret from %s', config.oidc.client_secret_path) + log.error( + 'CTXT', + 'Failed to read OIDC client secret from %s', + config.oidc.client_secret_path, + ); } } } - if ( - (issuer ?? client ?? secret) - && !(issuer && client && secret) - ) { - throw new Error('OIDC configuration is incomplete') + if ((issuer ?? client ?? secret) && !(issuer && client && secret)) { + throw new Error('OIDC configuration is incomplete'); } if (!issuer || !client || !secret) { - return + return; } if (config.oidc.only_start_if_oidc_is_available) { - log.debug('CTXT', 'Validating OIDC configuration from headscale config') - const result = await testOidc(issuer, client, secret) + log.debug('CTXT', 'Validating OIDC configuration from headscale config'); + const result = await testOidc(issuer, client, secret); if (!result) { - return + return; } } else { - log.debug('CTXT', 'OIDC validation is disabled in headscale config') - log.debug('CTXT', 'Skipping OIDC configuration validation') + log.debug('CTXT', 'OIDC validation is disabled in headscale config'); + log.debug('CTXT', 'Skipping OIDC configuration validation'); } return { @@ -245,5 +253,5 @@ async function checkOidc(config?: HeadscaleConfig) { rootKey, method, disableKeyLogin, - } + }; } diff --git a/app/utils/config/headscale.ts b/app/utils/config/headscale.ts index be9b333..6fdba36 100644 --- a/app/utils/config/headscale.ts +++ b/app/utils/config/headscale.ts @@ -6,29 +6,31 @@ // Around the codebase, this is referred to as the config // Refer to this file on juanfont/headscale for the default values: // https://github.com/juanfont/headscale/blob/main/hscontrol/types/config.go -import { readFile, writeFile } from 'node:fs/promises' -import { resolve } from 'node:path' +import { readFile, writeFile } from 'node:fs/promises'; +import { resolve } from 'node:path'; -import { type Document, parseDocument } from 'yaml' -import { z } from 'zod' +import { type Document, parseDocument } from 'yaml'; +import { z } from 'zod'; -import log from '~/utils/log' +import log from '~/utils/log'; const goBool = z .union([z.boolean(), z.literal('true'), z.literal('false')]) .transform((value) => { if (typeof value === 'boolean') { - return value + return value; } - return value === 'true' - }) + return value === 'true'; + }); -const goDuration = z.union([z.literal(0), z.string()]) +const goDuration = z.union([z.literal(0), z.string()]); const HeadscaleConfig = z.object({ tls_letsencrypt_cache_dir: z.string().default('/var/www/cache'), - tls_letsencrypt_challenge_type: z.enum(['HTTP-01', 'TLS-ALPN-01']).default('HTTP-01'), + tls_letsencrypt_challenge_type: z + .enum(['HTTP-01', 'TLS-ALPN-01']) + .default('HTTP-01'), tls_letsencrypt_hostname: z.string().optional(), tls_letsencrypt_listen: z.string().optional(), @@ -52,35 +54,45 @@ const HeadscaleConfig = z.object({ unix_socket: z.string().default('/var/run/headscale/headscale.sock'), unix_socket_permission: z.string().default('0o770'), - policy: z.object({ - mode: z.enum(['file', 'database']).default('file'), - path: z.string().optional(), - }).optional(), + policy: z + .object({ + mode: z.enum(['file', 'database']).default('file'), + path: z.string().optional(), + }) + .optional(), - tuning: z.object({ - batch_change_delay: goDuration.default('800ms'), - node_mapsession_buffered_chan_size: z.number().default(30), - }).optional(), + tuning: z + .object({ + batch_change_delay: goDuration.default('800ms'), + node_mapsession_buffered_chan_size: z.number().default(30), + }) + .optional(), noise: z.object({ private_key_path: z.string(), }), - log: z.object({ - level: z.string().default('info'), - format: z.enum(['text', 'json']).default('text'), - }).default({ level: 'info', format: 'text' }), + log: z + .object({ + level: z.string().default('info'), + format: z.enum(['text', 'json']).default('text'), + }) + .default({ level: 'info', format: 'text' }), - logtail: z.object({ - enabled: goBool.default(false), - }).default({ enabled: false }), + logtail: z + .object({ + enabled: goBool.default(false), + }) + .default({ enabled: false }), - cli: z.object({ - address: z.string().optional(), - api_key: z.string().optional(), - timeout: goDuration.default('10s'), - insecure: goBool.default(false), - }).optional(), + cli: z + .object({ + address: z.string().optional(), + api_key: z.string().optional(), + timeout: goDuration.default('10s'), + insecure: goBool.default(false), + }) + .optional(), prefixes: z.object({ allocation: z.enum(['sequential', 'random']).default('sequential'), @@ -91,34 +103,42 @@ const HeadscaleConfig = z.object({ dns: z.object({ magic_dns: goBool.default(true), base_domain: z.string().default('headscale.net'), - nameservers: z.object({ - global: z.array(z.string()).default([]), - split: z.record(z.array(z.string())).default({}), - }).default({ global: [], split: {} }), + nameservers: z + .object({ + global: z.array(z.string()).default([]), + split: z.record(z.array(z.string())).default({}), + }) + .default({ global: [], split: {} }), search_domains: z.array(z.string()).default([]), - extra_records: z.array(z.object({ - name: z.string(), - type: z.literal('A'), - value: z.string(), - })).default([]), + extra_records: z + .array( + z.object({ + name: z.string(), + type: z.literal('A'), + value: z.string(), + }), + ) + .default([]), use_username_in_magic_dns: goBool.default(false), }), - oidc: z.object({ - only_start_if_oidc_is_available: goBool.default(false), - issuer: z.string().optional(), - client_id: z.string().optional(), - client_secret: z.string().optional(), - client_secret_path: z.string().optional(), - scope: z.array(z.string()).default(['openid', 'profile', 'email']), - extra_params: z.record(z.unknown()).default({}), - allowed_domains: z.array(z.string()).optional(), - allowed_users: z.array(z.string()).optional(), - allowed_groups: z.array(z.string()).optional(), - strip_email_domain: goBool.default(false), - expiry: goDuration.default('180d'), - use_expiry_from_token: goBool.default(false), - }).optional(), + oidc: z + .object({ + only_start_if_oidc_is_available: goBool.default(false), + issuer: z.string().optional(), + client_id: z.string().optional(), + client_secret: z.string().optional(), + client_secret_path: z.string().optional(), + scope: z.array(z.string()).default(['openid', 'profile', 'email']), + extra_params: z.record(z.unknown()).default({}), + allowed_domains: z.array(z.string()).optional(), + allowed_users: z.array(z.string()).optional(), + allowed_groups: z.array(z.string()).optional(), + strip_email_domain: goBool.default(false), + expiry: goDuration.default('180d'), + use_expiry_from_token: goBool.default(false), + }) + .optional(), database: z.union([ z.object({ @@ -171,33 +191,35 @@ const HeadscaleConfig = z.object({ auto_update_enabled: goBool.default(true), update_frequency: goDuration.default('24h'), }), -}) +}); -export type HeadscaleConfig = z.infer +export type HeadscaleConfig = z.infer; -export let configYaml: Document | undefined -export let config: HeadscaleConfig | undefined +export let configYaml: Document | undefined; +export let config: HeadscaleConfig | undefined; export async function loadConfig(path?: string) { if (config) { - return config + return config; } if (!path) { - throw new Error('Path is required to lazy load config') + throw new Error('Path is required to lazy load config'); } - log.debug('CFGX', 'Loading Headscale configuration from %s', path) - const data = await readFile(path, 'utf8') - configYaml = parseDocument(data) + log.debug('CFGX', 'Loading Headscale configuration from %s', path); + const data = await readFile(path, 'utf8'); + configYaml = parseDocument(data); if (process.env.HEADSCALE_CONFIG_UNSTRICT === 'true') { - log.debug('CFGX', 'Loaded Headscale configuration in non-strict mode') - const loaded = configYaml.toJSON() as Record + log.debug('CFGX', 'Loaded Headscale configuration in non-strict mode'); + const loaded = configYaml.toJSON() as Record; config = { ...loaded, - tls_letsencrypt_cache_dir: loaded.tls_letsencrypt_cache_dir ?? '/var/www/cache', - tls_letsencrypt_challenge_type: loaded.tls_letsencrypt_challenge_type ?? 'HTTP-01', + tls_letsencrypt_cache_dir: + loaded.tls_letsencrypt_cache_dir ?? '/var/www/cache', + tls_letsencrypt_challenge_type: + loaded.tls_letsencrypt_challenge_type ?? 'HTTP-01', grpc_listen_addr: loaded.grpc_listen_addr ?? ':50443', grpc_allow_insecure: loaded.grpc_allow_insecure ?? false, randomize_client_port: loaded.randomize_client_port ?? false, @@ -238,54 +260,54 @@ export async function loadConfig(path?: string) { magic_dns: false, base_domain: 'headscale.net', }, - } as HeadscaleConfig + } as HeadscaleConfig; - log.warn('CFGX', 'Loaded Headscale configuration in non-strict mode') - log.warn('CFGX', 'By using this mode you forfeit GitHub issue support') - log.warn('CFGX', 'This is very dangerous and comes with a few caveats:') - log.warn('CFGX', 'Headplane could very easily crash') - log.warn('CFGX', 'Headplane could break your Headscale installation') - log.warn('CFGX', 'The UI could throw random errors/show incorrect data') - log.warn('CFGX', '') - return config + log.warn('CFGX', 'Loaded Headscale configuration in non-strict mode'); + log.warn('CFGX', 'By using this mode you forfeit GitHub issue support'); + log.warn('CFGX', 'This is very dangerous and comes with a few caveats:'); + log.warn('CFGX', 'Headplane could very easily crash'); + log.warn('CFGX', 'Headplane could break your Headscale installation'); + log.warn('CFGX', 'The UI could throw random errors/show incorrect data'); + log.warn('CFGX', ''); + return config; } try { - log.debug('CFGX', 'Attempting to parse Headscale configuration') - config = await HeadscaleConfig.parseAsync(configYaml.toJSON()) + log.debug('CFGX', 'Attempting to parse Headscale configuration'); + config = await HeadscaleConfig.parseAsync(configYaml.toJSON()); } catch (error) { - log.debug('CFGX', 'Failed to load Headscale configuration') + log.debug('CFGX', 'Failed to load Headscale configuration'); if (error instanceof z.ZodError) { - log.error('CFGX', 'Recieved invalid configuration file') - log.error('CFGX', 'The following schema issues were found:') + log.error('CFGX', 'Recieved invalid configuration file'); + log.error('CFGX', 'The following schema issues were found:'); for (const issue of error.issues) { - const path = issue.path.map(String).join('.') - const message = issue.message + const path = issue.path.map(String).join('.'); + const message = issue.message; - log.error('CFGX', ` '${path}': ${message}`) + log.error('CFGX', ` '${path}': ${message}`); } - log.error('CFGX', '') - log.error('CFGX', 'Resolve these issues and try again.') - log.error('CFGX', 'Headplane will operate without the config') - log.error('CFGX', '') + log.error('CFGX', ''); + log.error('CFGX', 'Resolve these issues and try again.'); + log.error('CFGX', 'Headplane will operate without the config'); + log.error('CFGX', ''); } - throw error + throw error; } - return config + return config; } // This is so obscenely dangerous, please have a check around it export async function patchConfig(partial: Record) { if (!configYaml || !config) { - throw new Error('Config not loaded') + throw new Error('Config not loaded'); } - log.debug('CFGX', 'Patching Headscale configuration') + log.debug('CFGX', 'Patching Headscale configuration'); for (const [key, value] of Object.entries(partial)) { - log.debug('CFGX', 'Patching %s with %s', key, value) + log.debug('CFGX', 'Patching %s with %s', key, value); // If the key is something like `test.bar."foo.bar"`, then we treat // the foo.bar as a single key, and not as two keys, so that needs // to be split correctly. @@ -294,39 +316,40 @@ export async function patchConfig(partial: Record) { // the next character is a quote, and if it is, we skip until the next // quote, and then we skip the next character, which should be a dot. // If it's not a quote, we split it. - const path = [] - let temp = '' - let inQuote = false + const path = []; + let temp = ''; + let inQuote = false; for (const element of key) { if (element === '"') { - inQuote = !inQuote + inQuote = !inQuote; } if (element === '.' && !inQuote) { - path.push(temp.replaceAll('"', '')) - temp = '' - continue + path.push(temp.replaceAll('"', '')); + temp = ''; + continue; } - temp += element + temp += element; } // Push the remaining element - path.push(temp.replaceAll('"', '')) + path.push(temp.replaceAll('"', '')); if (value === null) { - configYaml.deleteIn(path) - continue + configYaml.deleteIn(path); + continue; } - configYaml.setIn(path, value) + configYaml.setIn(path, value); } - config = process.env.HEADSCALE_CONFIG_UNSTRICT === 'true' - ? configYaml.toJSON() as HeadscaleConfig - : (await HeadscaleConfig.parseAsync(configYaml.toJSON())) + config = + process.env.HEADSCALE_CONFIG_UNSTRICT === 'true' + ? (configYaml.toJSON() as HeadscaleConfig) + : await HeadscaleConfig.parseAsync(configYaml.toJSON()); - const path = resolve(process.env.CONFIG_FILE ?? '/etc/headscale/config.yaml') - log.debug('CFGX', 'Writing patched configuration to %s', path) - await writeFile(path, configYaml.toString(), 'utf8') + const path = resolve(process.env.CONFIG_FILE ?? '/etc/headscale/config.yaml'); + log.debug('CFGX', 'Writing patched configuration to %s', path); + await writeFile(path, configYaml.toString(), 'utf8'); } diff --git a/app/utils/headscale.ts b/app/utils/headscale.ts index 3ebbe3f..18d9d14 100644 --- a/app/utils/headscale.ts +++ b/app/utils/headscale.ts @@ -1,116 +1,138 @@ -import { loadContext } from './config/headplane' -import log from './log' +import { loadContext } from './config/headplane'; +import log from './log'; export class HeadscaleError extends Error { - status: number + status: number; constructor(message: string, status: number) { - super(message) - this.name = 'HeadscaleError' - this.status = status + super(message); + this.name = 'HeadscaleError'; + this.status = status; } } export class FatalError extends Error { constructor() { - super('The Headscale server is not accessible or the supplied API key is invalid') - this.name = 'FatalError' + super( + 'The Headscale server is not accessible or the supplied API key is invalid', + ); + this.name = 'FatalError'; } } export async function pull(url: string, key: string) { if (!key || key === 'undefined' || key.length === 0) { - throw new Error('Missing API key, could this be a cookie setting issue?') + throw new Error('Missing API key, could this be a cookie setting issue?'); } - const context = await loadContext() - const prefix = context.headscaleUrl + const context = await loadContext(); + const prefix = context.headscaleUrl; - log.debug('APIC', 'GET %s', `${prefix}/api/${url}`) + log.debug('APIC', 'GET %s', `${prefix}/api/${url}`); const response = await fetch(`${prefix}/api/${url}`, { headers: { Authorization: `Bearer ${key}`, }, - }) + }); if (!response.ok) { - log.debug('APIC', 'GET %s failed with status %d', `${prefix}/api/${url}`, response.status) - throw new HeadscaleError(await response.text(), response.status) + log.debug( + 'APIC', + 'GET %s failed with status %d', + `${prefix}/api/${url}`, + response.status, + ); + throw new HeadscaleError(await response.text(), response.status); } - return (response.json() as Promise) + return response.json() as Promise; } export async function post(url: string, key: string, body?: unknown) { if (!key || key === 'undefined' || key.length === 0) { - throw new Error('Missing API key, could this be a cookie setting issue?') + throw new Error('Missing API key, could this be a cookie setting issue?'); } - const context = await loadContext() - const prefix = context.headscaleUrl + const context = await loadContext(); + const prefix = context.headscaleUrl; - log.debug('APIC', 'POST %s', `${prefix}/api/${url}`) + log.debug('APIC', 'POST %s', `${prefix}/api/${url}`); const response = await fetch(`${prefix}/api/${url}`, { method: 'POST', body: body ? JSON.stringify(body) : undefined, headers: { Authorization: `Bearer ${key}`, }, - }) + }); if (!response.ok) { - log.debug('APIC', 'POST %s failed with status %d', `${prefix}/api/${url}`, response.status) - throw new HeadscaleError(await response.text(), response.status) + log.debug( + 'APIC', + 'POST %s failed with status %d', + `${prefix}/api/${url}`, + response.status, + ); + throw new HeadscaleError(await response.text(), response.status); } - return (response.json() as Promise) + return response.json() as Promise; } export async function put(url: string, key: string, body?: unknown) { if (!key || key === 'undefined' || key.length === 0) { - throw new Error('Missing API key, could this be a cookie setting issue?') + throw new Error('Missing API key, could this be a cookie setting issue?'); } - const context = await loadContext() - const prefix = context.headscaleUrl + const context = await loadContext(); + const prefix = context.headscaleUrl; - log.debug('APIC', 'PUT %s', `${prefix}/api/${url}`) + log.debug('APIC', 'PUT %s', `${prefix}/api/${url}`); const response = await fetch(`${prefix}/api/${url}`, { method: 'PUT', body: body ? JSON.stringify(body) : undefined, headers: { Authorization: `Bearer ${key}`, }, - }) + }); if (!response.ok) { - log.debug('APIC', 'PUT %s failed with status %d', `${prefix}/api/${url}`, response.status) - throw new HeadscaleError(await response.text(), response.status) + log.debug( + 'APIC', + 'PUT %s failed with status %d', + `${prefix}/api/${url}`, + response.status, + ); + throw new HeadscaleError(await response.text(), response.status); } - return (response.json() as Promise) + return response.json() as Promise; } export async function del(url: string, key: string) { if (!key || key === 'undefined' || key.length === 0) { - throw new Error('Missing API key, could this be a cookie setting issue?') + throw new Error('Missing API key, could this be a cookie setting issue?'); } - const context = await loadContext() - const prefix = context.headscaleUrl + const context = await loadContext(); + const prefix = context.headscaleUrl; - log.debug('APIC', 'DELETE %s', `${prefix}/api/${url}`) + log.debug('APIC', 'DELETE %s', `${prefix}/api/${url}`); const response = await fetch(`${prefix}/api/${url}`, { method: 'DELETE', headers: { Authorization: `Bearer ${key}`, }, - }) + }); if (!response.ok) { - log.debug('APIC', 'DELETE %s failed with status %d', `${prefix}/api/${url}`, response.status) - throw new HeadscaleError(await response.text(), response.status) + log.debug( + 'APIC', + 'DELETE %s failed with status %d', + `${prefix}/api/${url}`, + response.status, + ); + throw new HeadscaleError(await response.text(), response.status); } - return (response.json() as Promise) + return response.json() as Promise; } diff --git a/app/utils/log.ts b/app/utils/log.ts index ce1f7a1..91ff7dc 100644 --- a/app/utils/log.ts +++ b/app/utils/log.ts @@ -1,22 +1,22 @@ export default { info: (category: string, message: string, ...args: unknown[]) => { - defaultLog('INFO', category, message, ...args) + defaultLog('INFO', category, message, ...args); }, warn: (category: string, message: string, ...args: unknown[]) => { - defaultLog('WARN', category, message, ...args) + defaultLog('WARN', category, message, ...args); }, error: (category: string, message: string, ...args: unknown[]) => { - defaultLog('ERRO', category, message, ...args) + defaultLog('ERRO', category, message, ...args); }, debug: (category: string, message: string, ...args: unknown[]) => { if (process.env.DEBUG === 'true') { - defaultLog('DEBG', category, message, ...args) + defaultLog('DEBG', category, message, ...args); } - } -} + }, +}; function defaultLog( level: string, @@ -24,6 +24,6 @@ function defaultLog( message: string, ...args: unknown[] ) { - const date = new Date().toISOString() - console.log(`${date} (${level}) [${category}] ${message}`, ...args) + const date = new Date().toISOString(); + console.log(`${date} (${level}) [${category}] ${message}`, ...args); } diff --git a/app/utils/oidc.ts b/app/utils/oidc.ts index e512080..a679f1e 100644 --- a/app/utils/oidc.ts +++ b/app/utils/oidc.ts @@ -1,4 +1,4 @@ -import { redirect } from '@remix-run/node' +import { redirect } from 'react-router'; import { authorizationCodeGrantRequest, calculatePKCECodeChallenge, @@ -13,99 +13,99 @@ import { processAuthorizationCodeOpenIDResponse, processDiscoveryResponse, validateAuthResponse, -} from 'oauth4webapi' +} from 'oauth4webapi'; -import { post } from '~/utils/headscale' -import { commitSession, getSession } from '~/utils/sessions' -import log from '~/utils/log' +import { post } from '~/utils/headscale'; +import { commitSession, getSession } from '~/utils/sessions'; +import log from '~/utils/log'; -import { HeadplaneContext } from './config/headplane' +import { HeadplaneContext } from './config/headplane'; -type OidcConfig = NonNullable +type OidcConfig = NonNullable; export async function startOidc(oidc: OidcConfig, req: Request) { - const session = await getSession(req.headers.get('Cookie')) + const session = await getSession(req.headers.get('Cookie')); if (session.has('hsApiKey')) { return redirect('/', { status: 302, headers: { 'Set-Cookie': await commitSession(session), }, - }) + }); } - const issuerUrl = new URL(oidc.issuer) + const issuerUrl = new URL(oidc.issuer); const oidcClient = { client_id: oidc.client, token_endpoint_auth_method: oidc.method, - } satisfies Client + } satisfies Client; - const response = await discoveryRequest(issuerUrl) - const processed = await processDiscoveryResponse(issuerUrl, response) + const response = await discoveryRequest(issuerUrl); + const processed = await processDiscoveryResponse(issuerUrl, response); if (!processed.authorization_endpoint) { - throw new Error('No authorization endpoint found on the OIDC provider') + throw new Error('No authorization endpoint found on the OIDC provider'); } - const state = generateRandomState() - const nonce = generateRandomNonce() - const verifier = generateRandomCodeVerifier() - const challenge = await calculatePKCECodeChallenge(verifier) + const state = generateRandomState(); + const nonce = generateRandomNonce(); + const verifier = generateRandomCodeVerifier(); + const challenge = await calculatePKCECodeChallenge(verifier); - const callback = new URL('/admin/oidc/callback', req.url) - callback.protocol = req.headers.get('X-Forwarded-Proto') ?? 'http:' - callback.host = req.headers.get('Host') ?? '' - const authUrl = new URL(processed.authorization_endpoint) + const callback = new URL('/admin/oidc/callback', req.url); + callback.protocol = req.headers.get('X-Forwarded-Proto') ?? 'http:'; + callback.host = req.headers.get('Host') ?? ''; + const authUrl = new URL(processed.authorization_endpoint); - authUrl.searchParams.set('client_id', oidcClient.client_id) - authUrl.searchParams.set('response_type', 'code') - authUrl.searchParams.set('redirect_uri', callback.href) - authUrl.searchParams.set('scope', 'openid profile email') - authUrl.searchParams.set('code_challenge', challenge) - authUrl.searchParams.set('code_challenge_method', 'S256') - authUrl.searchParams.set('state', state) - authUrl.searchParams.set('nonce', nonce) + authUrl.searchParams.set('client_id', oidcClient.client_id); + authUrl.searchParams.set('response_type', 'code'); + authUrl.searchParams.set('redirect_uri', callback.href); + authUrl.searchParams.set('scope', 'openid profile email'); + authUrl.searchParams.set('code_challenge', challenge); + authUrl.searchParams.set('code_challenge_method', 'S256'); + authUrl.searchParams.set('state', state); + authUrl.searchParams.set('nonce', nonce); - session.set('authState', state) - session.set('authNonce', nonce) - session.set('authVerifier', verifier) + session.set('authState', state); + session.set('authNonce', nonce); + session.set('authVerifier', verifier); return redirect(authUrl.href, { status: 302, headers: { 'Set-Cookie': await commitSession(session), }, - }) + }); } export async function finishOidc(oidc: OidcConfig, req: Request) { - const session = await getSession(req.headers.get('Cookie')) + const session = await getSession(req.headers.get('Cookie')); if (session.has('hsApiKey')) { return redirect('/', { status: 302, headers: { 'Set-Cookie': await commitSession(session), }, - }) + }); } - const issuerUrl = new URL(oidc.issuer) + const issuerUrl = new URL(oidc.issuer); const oidcClient = { client_id: oidc.client, client_secret: oidc.secret, token_endpoint_auth_method: oidc.method, - } satisfies Client + } satisfies Client; - const response = await discoveryRequest(issuerUrl) - const processed = await processDiscoveryResponse(issuerUrl, response) + const response = await discoveryRequest(issuerUrl); + const processed = await processDiscoveryResponse(issuerUrl, response); if (!processed.authorization_endpoint) { - throw new Error('No authorization endpoint found on the OIDC provider') + throw new Error('No authorization endpoint found on the OIDC provider'); } - const state = session.get('authState') - const nonce = session.get('authNonce') - const verifier = session.get('authVerifier') + const state = session.get('authState'); + const nonce = session.get('authNonce'); + const verifier = session.get('authVerifier'); if (!state || !nonce || !verifier) { - throw new Error('No OIDC state found in the session') + throw new Error('No OIDC state found in the session'); } const parameters = validateAuthResponse( @@ -113,15 +113,15 @@ export async function finishOidc(oidc: OidcConfig, req: Request) { oidcClient, new URL(req.url), state, - ) + ); if (isOAuth2Error(parameters)) { - throw new Error('Invalid response from the OIDC provider') + throw new Error('Invalid response from the OIDC provider'); } - const callback = new URL('/admin/oidc/callback', req.url) - callback.protocol = req.headers.get('X-Forwarded-Proto') ?? 'http:' - callback.host = req.headers.get('Host') ?? '' + const callback = new URL('/admin/oidc/callback', req.url); + callback.protocol = req.headers.get('X-Forwarded-Proto') ?? 'http:'; + callback.host = req.headers.get('Host') ?? ''; const tokenResponse = await authorizationCodeGrantRequest( processed, @@ -129,11 +129,11 @@ export async function finishOidc(oidc: OidcConfig, req: Request) { parameters, callback.href, verifier, - ) + ); - const challenges = parseWwwAuthenticateChallenges(tokenResponse) + const challenges = parseWwwAuthenticateChallenges(tokenResponse); if (challenges) { - throw new Error('Recieved a challenge from the OIDC provider') + throw new Error('Recieved a challenge from the OIDC provider'); } const result = await processAuthorizationCodeOpenIDResponse( @@ -141,14 +141,14 @@ export async function finishOidc(oidc: OidcConfig, req: Request) { oidcClient, tokenResponse, nonce, - ) + ); if (isOAuth2Error(result)) { - throw new Error('Invalid response from the OIDC provider') + throw new Error('Invalid response from the OIDC provider'); } - const claims = getValidatedIdTokenClaims(result) - const expDate = new Date(claims.exp * 1000).toISOString() + const claims = getValidatedIdTokenClaims(result); + const expDate = new Date(claims.exp * 1000).toISOString(); const keyResponse = await post<{ apiKey: string }>( 'v1/apikey', @@ -156,19 +156,19 @@ export async function finishOidc(oidc: OidcConfig, req: Request) { { expiration: expDate, }, - ) + ); - session.set('hsApiKey', keyResponse.apiKey) + session.set('hsApiKey', keyResponse.apiKey); session.set('user', { name: claims.name ? String(claims.name) : 'Anonymous', email: claims.email ? String(claims.email) : undefined, - }) + }); return redirect('/machines', { headers: { 'Set-Cookie': await commitSession(session), }, - }) + }); } // Runs at application startup to validate the OIDC configuration @@ -177,23 +177,27 @@ export async function testOidc(issuer: string, client: string, secret: string) { client_id: client, client_secret: secret, token_endpoint_auth_method: 'client_secret_post', - } satisfies Client + } satisfies Client; - const issuerUrl = new URL(issuer) + const issuerUrl = new URL(issuer); try { - log.debug('OIDC', 'Checking OIDC well-known endpoint') - const response = await discoveryRequest(issuerUrl) - const processed = await processDiscoveryResponse(issuerUrl, response) + log.debug('OIDC', 'Checking OIDC well-known endpoint'); + const response = await discoveryRequest(issuerUrl); + const processed = await processDiscoveryResponse(issuerUrl, response); if (!processed.authorization_endpoint) { - log.debug('OIDC', 'No authorization endpoint found on the OIDC provider') - return false + log.debug('OIDC', 'No authorization endpoint found on the OIDC provider'); + return false; } - log.debug('OIDC', 'Found auth endpoint: %s', processed.authorization_endpoint) - return true + log.debug( + 'OIDC', + 'Found auth endpoint: %s', + processed.authorization_endpoint, + ); + return true; } catch (e) { - log.debug('OIDC', 'Validation failed: %s', e.message) - return false + log.debug('OIDC', 'Validation failed: %s', e.message); + return false; } } diff --git a/app/utils/res.ts b/app/utils/res.ts index 5329a19..c6f0258 100644 --- a/app/utils/res.ts +++ b/app/utils/res.ts @@ -1,5 +1,5 @@ -import { data } from '@remix-run/node' +import { data } from 'react-router'; export function send(payload: T, init?: number | ResponseInit) { - return data(payload, init) + return data(payload, init); } diff --git a/app/utils/sessions.ts b/app/utils/sessions.ts index 98a7bcf..328ce0e 100644 --- a/app/utils/sessions.ts +++ b/app/utils/sessions.ts @@ -1,4 +1,4 @@ -import { createCookieSessionStorage } from '@remix-run/node' // Or cloudflare/deno +import { createCookieSessionStorage } from 'react-router'; // Or cloudflare/deno export type SessionData = { hsApiKey: string; @@ -9,18 +9,14 @@ export type SessionData = { name: string; email?: string; }; -} +}; type SessionFlashData = { error: string; -} +}; -export const { - getSession, - commitSession, - destroySession -} = createCookieSessionStorage( - { +export const { getSession, commitSession, destroySession } = + createCookieSessionStorage({ cookie: { name: 'hp_sess', httpOnly: true, @@ -29,7 +25,5 @@ export const { sameSite: 'lax', secrets: [process.env.COOKIE_SECRET!], secure: process.env.COOKIE_SECURE !== 'false', - } - } -) - + }, + }); diff --git a/app/utils/useLiveData.ts b/app/utils/useLiveData.ts index c7a861f..68c2cb9 100644 --- a/app/utils/useLiveData.ts +++ b/app/utils/useLiveData.ts @@ -1,35 +1,35 @@ -import { useRevalidator } from '@remix-run/react' -import { useEffect } from 'react' -import { useInterval } from 'usehooks-ts' +import { useRevalidator } from 'react-router'; +import { useEffect } from 'react'; +import { useInterval } from 'usehooks-ts'; interface Props { - interval: number + interval: number; } export function useLiveData({ interval }: Props) { - const revalidator = useRevalidator() + const revalidator = useRevalidator(); // Handle normal stale-while-revalidate behavior useInterval(() => { if (revalidator.state === 'idle') { - revalidator.revalidate() + revalidator.revalidate(); } - }, interval) + }, interval); useEffect(() => { const handler = () => { if (revalidator.state === 'idle') { - revalidator.revalidate() + revalidator.revalidate(); } - } + }; - window.addEventListener('online', handler) - document.addEventListener('focus', handler) + window.addEventListener('online', handler); + document.addEventListener('focus', handler); return () => { - window.removeEventListener('online', handler) - document.removeEventListener('focus', handler) - } - }, [revalidator]) - return revalidator + window.removeEventListener('online', handler); + document.removeEventListener('focus', handler); + }; + }, [revalidator]); + return revalidator; } diff --git a/app/utils/ws.ts b/app/utils/ws.ts index cf81327..33bed07 100644 --- a/app/utils/ws.ts +++ b/app/utils/ws.ts @@ -1,47 +1,47 @@ // This is a "side-effect" but we want a lifecycle cache map of // peer statuses to prevent unnecessary fetches to the agent. -import type { LoaderFunctionArgs } from 'remix' +import type { LoaderFunctionArgs } from 'react-router'; -type Context = LoaderFunctionArgs['context'] -const cache: { [nodeID: string]: unknown } = {} +type Context = LoaderFunctionArgs['context']; +const cache: { [nodeID: string]: unknown } = {}; export async function queryWS(context: Context, nodeIDs: string[]) { - const ws = context.ws - const firstClient = ws.clients.values().next().value + const ws = context.ws; + const firstClient = ws.clients.values().next().value; if (!firstClient) { - return cache + return cache; } const cached = nodeIDs.map((nodeID) => { - const cached = cache[nodeID] + const cached = cache[nodeID]; if (cached) { - return cached + return cached; } - }) + }); // We only need to query the nodes that are not cached - const uncached = nodeIDs.filter((nodeID) => !cached.includes(nodeID)) + const uncached = nodeIDs.filter((nodeID) => !cached.includes(nodeID)); if (uncached.length === 0) { - return cache + return cache; } - firstClient.send(JSON.stringify({ NodeIDs: uncached })) - await new Promise((resolve) => { + firstClient.send(JSON.stringify({ NodeIDs: uncached })); + await new Promise((resolve) => { const timeout = setTimeout(() => { - resolve() - }, 3000) + resolve(); + }, 3000); - firstClient.on('message', (message) => { - const data = JSON.parse(message.toString()) + firstClient.on('message', (message: string) => { + const data = JSON.parse(message.toString()); if (Object.keys(data).length === 0) { - resolve() + resolve(); } for (const [nodeID, status] of Object.entries(data)) { - cache[nodeID] = status + cache[nodeID] = status; } - }) - }) + }); + }); - return cache + return cache; } diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..bb2193f --- /dev/null +++ b/biome.json @@ -0,0 +1,33 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", + "vcs": { + "enabled": false, + "clientKind": "git", + "useIgnoreFile": false + }, + "files": { + "ignoreUnknown": false, + "ignore": [] + }, + "formatter": { + "enabled": true, + "indentStyle": "tab", + "lineWidth": 80, + "lineEnding": "lf" + }, + "organizeImports": { + "enabled": true + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + }, + "javascript": { + "formatter": { + "quoteStyle": "single", + "jsxQuoteStyle": "double" + } + } +} diff --git a/package.json b/package.json index dc2abb2..0084fb8 100644 --- a/package.json +++ b/package.json @@ -17,9 +17,8 @@ "@kubernetes/client-node": "^0.22.3", "@primer/octicons-react": "^19.13.0", "@react-aria/toast": "3.0.0-beta.18", + "@react-router/node": "^7.0.0", "@react-stately/toast": "3.0.0-beta.7", - "@remix-run/node": "^2.15.0", - "@remix-run/react": "^2.15.0", "@shopify/lang-jsonc": "^1.0.0", "@types/react": "^19.0.1", "@types/react-dom": "^19.0.1", @@ -35,6 +34,7 @@ "react-codemirror-merge": "^4.23.6", "react-dom": "19.0.0", "react-error-boundary": "^4.1.2", + "react-router": "^7.0.0", "remix-utils": "^7.7.0", "tailwind-merge": "^2.5.5", "tailwindcss-react-aria-components": "^1.2.0", @@ -46,8 +46,8 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.26.0", - "@remix-run/dev": "^2.15.0", - "@remix-run/route-config": "^2.15.0", + "@biomejs/biome": "^1.9.4", + "@react-router/dev": "^7.0.0", "autoprefixer": "^10.4.20", "babel-plugin-react-compiler": "19.0.0-beta-df7b47d-20241124", "postcss": "^8.4.49", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index db7b538..9bd4f39 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -34,15 +34,12 @@ importers: '@react-aria/toast': specifier: 3.0.0-beta.18 version: 3.0.0-beta.18(react@19.0.0) + '@react-router/node': + specifier: ^7.0.0 + version: 7.1.1(react-router@7.1.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(typescript@5.7.2) '@react-stately/toast': specifier: 3.0.0-beta.7 version: 3.0.0-beta.7(react@19.0.0) - '@remix-run/node': - specifier: ^2.15.0 - version: 2.15.0(typescript@5.7.2) - '@remix-run/react': - specifier: ^2.15.0 - version: 2.15.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2) '@shopify/lang-jsonc': specifier: ^1.0.0 version: 1.0.0(patch_hash=kv4he7q622clo6pnx2dz7va2yu) @@ -88,6 +85,9 @@ importers: react-error-boundary: specifier: ^4.1.2 version: 4.1.2(react@19.0.0) + react-router: + specifier: ^7.0.0 + version: 7.1.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) remix-utils: specifier: ^7.7.0 version: 7.7.0(@remix-run/node@2.15.0(typescript@5.7.2))(@remix-run/react@2.15.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2))(@remix-run/router@1.21.0)(react@19.0.0)(zod@3.23.8) @@ -116,12 +116,12 @@ importers: '@babel/preset-typescript': specifier: ^7.26.0 version: 7.26.0(@babel/core@7.26.0) - '@remix-run/dev': - specifier: ^2.15.0 - version: 2.15.0(@remix-run/react@2.15.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2))(@types/node@22.10.1)(typescript@5.7.2)(vite@6.0.3(@types/node@22.10.1)(jiti@1.21.6)(yaml@2.6.1)) - '@remix-run/route-config': - specifier: ^2.15.0 - version: 2.15.0(@remix-run/dev@2.15.0(@remix-run/react@2.15.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2))(@types/node@22.10.1)(typescript@5.7.2)(vite@6.0.3(@types/node@22.10.1)(jiti@1.21.6)(yaml@2.6.1)))(typescript@5.7.2) + '@biomejs/biome': + specifier: ^1.9.4 + version: 1.9.4 + '@react-router/dev': + specifier: ^7.0.0 + version: 7.1.1(@types/node@22.10.1)(jiti@1.21.6)(react-router@7.1.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(typescript@5.7.2)(vite@6.0.3(@types/node@22.10.1)(jiti@1.21.6)(yaml@2.6.1))(yaml@2.6.1) autoprefixer: specifier: ^10.4.20 version: 10.4.20(postcss@8.4.49) @@ -299,6 +299,59 @@ packages: resolution: {integrity: sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==} engines: {node: '>=6.9.0'} + '@biomejs/biome@1.9.4': + resolution: {integrity: sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==} + engines: {node: '>=14.21.3'} + hasBin: true + + '@biomejs/cli-darwin-arm64@1.9.4': + resolution: {integrity: sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [darwin] + + '@biomejs/cli-darwin-x64@1.9.4': + resolution: {integrity: sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [darwin] + + '@biomejs/cli-linux-arm64-musl@1.9.4': + resolution: {integrity: sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + + '@biomejs/cli-linux-arm64@1.9.4': + resolution: {integrity: sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + + '@biomejs/cli-linux-x64-musl@1.9.4': + resolution: {integrity: sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + + '@biomejs/cli-linux-x64@1.9.4': + resolution: {integrity: sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + + '@biomejs/cli-win32-arm64@1.9.4': + resolution: {integrity: sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [win32] + + '@biomejs/cli-win32-x64@1.9.4': + resolution: {integrity: sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [win32] + '@codemirror/autocomplete@6.18.2': resolution: {integrity: sha512-wJGylKtMFR/Ds6Gh01+OovXE/pncPiKZNNBKuC39pKnH+XK5d9+WsNqcrdxPjFPFTigRBqse0rfxw9UxrfyhPg==} peerDependencies: @@ -359,321 +412,108 @@ packages: peerDependencies: react: '>=16.8.0' - '@emotion/hash@0.9.2': - resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==} - - '@esbuild/aix-ppc64@0.21.5': - resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [aix] - '@esbuild/aix-ppc64@0.24.0': resolution: {integrity: sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.17.6': - resolution: {integrity: sha512-YnYSCceN/dUzUr5kdtUzB+wZprCafuD89Hs0Aqv9QSdwhYQybhXTaSTcrl6X/aWThn1a/j0eEpUBGOE7269REg==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm64@0.21.5': - resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - '@esbuild/android-arm64@0.24.0': resolution: {integrity: sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.17.6': - resolution: {integrity: sha512-bSC9YVUjADDy1gae8RrioINU6e1lCkg3VGVwm0QQ2E1CWcC4gnMce9+B6RpxuSsrsXsk1yojn7sp1fnG8erE2g==} - engines: {node: '>=12'} - cpu: [arm] - os: [android] - - '@esbuild/android-arm@0.21.5': - resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} - engines: {node: '>=12'} - cpu: [arm] - os: [android] - '@esbuild/android-arm@0.24.0': resolution: {integrity: sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.17.6': - resolution: {integrity: sha512-MVcYcgSO7pfu/x34uX9u2QIZHmXAB7dEiLQC5bBl5Ryqtpj9lT2sg3gNDEsrPEmimSJW2FXIaxqSQ501YLDsZQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - - '@esbuild/android-x64@0.21.5': - resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - '@esbuild/android-x64@0.24.0': resolution: {integrity: sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.17.6': - resolution: {integrity: sha512-bsDRvlbKMQMt6Wl08nHtFz++yoZHsyTOxnjfB2Q95gato+Yi4WnRl13oC2/PJJA9yLCoRv9gqT/EYX0/zDsyMA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-arm64@0.21.5': - resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - '@esbuild/darwin-arm64@0.24.0': resolution: {integrity: sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.17.6': - resolution: {integrity: sha512-xh2A5oPrYRfMFz74QXIQTQo8uA+hYzGWJFoeTE8EvoZGHb+idyV4ATaukaUvnnxJiauhs/fPx3vYhU4wiGfosg==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - - '@esbuild/darwin-x64@0.21.5': - resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - '@esbuild/darwin-x64@0.24.0': resolution: {integrity: sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.17.6': - resolution: {integrity: sha512-EnUwjRc1inT4ccZh4pB3v1cIhohE2S4YXlt1OvI7sw/+pD+dIE4smwekZlEPIwY6PhU6oDWwITrQQm5S2/iZgg==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-arm64@0.21.5': - resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - '@esbuild/freebsd-arm64@0.24.0': resolution: {integrity: sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.17.6': - resolution: {integrity: sha512-Uh3HLWGzH6FwpviUcLMKPCbZUAFzv67Wj5MTwK6jn89b576SR2IbEp+tqUHTr8DIl0iDmBAf51MVaP7pw6PY5Q==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.21.5': - resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - '@esbuild/freebsd-x64@0.24.0': resolution: {integrity: sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.17.6': - resolution: {integrity: sha512-bUR58IFOMJX523aDVozswnlp5yry7+0cRLCXDsxnUeQYJik1DukMY+apBsLOZJblpH+K7ox7YrKrHmJoWqVR9w==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm64@0.21.5': - resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - '@esbuild/linux-arm64@0.24.0': resolution: {integrity: sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.17.6': - resolution: {integrity: sha512-7YdGiurNt7lqO0Bf/U9/arrPWPqdPqcV6JCZda4LZgEn+PTQ5SMEI4MGR52Bfn3+d6bNEGcWFzlIxiQdS48YUw==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-arm@0.21.5': - resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - '@esbuild/linux-arm@0.24.0': resolution: {integrity: sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.17.6': - resolution: {integrity: sha512-ujp8uoQCM9FRcbDfkqECoARsLnLfCUhKARTP56TFPog8ie9JG83D5GVKjQ6yVrEVdMie1djH86fm98eY3quQkQ==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-ia32@0.21.5': - resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - '@esbuild/linux-ia32@0.24.0': resolution: {integrity: sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.17.6': - resolution: {integrity: sha512-y2NX1+X/Nt+izj9bLoiaYB9YXT/LoaQFYvCkVD77G/4F+/yuVXYCWz4SE9yr5CBMbOxOfBcy/xFL4LlOeNlzYQ==} - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-loong64@0.21.5': - resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - '@esbuild/linux-loong64@0.24.0': resolution: {integrity: sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.17.6': - resolution: {integrity: sha512-09AXKB1HDOzXD+j3FdXCiL/MWmZP0Ex9eR8DLMBVcHorrWJxWmY8Nms2Nm41iRM64WVx7bA/JVHMv081iP2kUA==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-mips64el@0.21.5': - resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - '@esbuild/linux-mips64el@0.24.0': resolution: {integrity: sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.17.6': - resolution: {integrity: sha512-AmLhMzkM8JuqTIOhxnX4ubh0XWJIznEynRnZAVdA2mMKE6FAfwT2TWKTwdqMG+qEaeyDPtfNoZRpJbD4ZBv0Tg==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-ppc64@0.21.5': - resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - '@esbuild/linux-ppc64@0.24.0': resolution: {integrity: sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.17.6': - resolution: {integrity: sha512-Y4Ri62PfavhLQhFbqucysHOmRamlTVK10zPWlqjNbj2XMea+BOs4w6ASKwQwAiqf9ZqcY9Ab7NOU4wIgpxwoSQ==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-riscv64@0.21.5': - resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - '@esbuild/linux-riscv64@0.24.0': resolution: {integrity: sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.17.6': - resolution: {integrity: sha512-SPUiz4fDbnNEm3JSdUW8pBJ/vkop3M1YwZAVwvdwlFLoJwKEZ9L98l3tzeyMzq27CyepDQ3Qgoba44StgbiN5Q==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-s390x@0.21.5': - resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - '@esbuild/linux-s390x@0.24.0': resolution: {integrity: sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.17.6': - resolution: {integrity: sha512-a3yHLmOodHrzuNgdpB7peFGPx1iJ2x6m+uDvhP2CKdr2CwOaqEFMeSqYAHU7hG+RjCq8r2NFujcd/YsEsFgTGw==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - - '@esbuild/linux-x64@0.21.5': - resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - '@esbuild/linux-x64@0.24.0': resolution: {integrity: sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-x64@0.17.6': - resolution: {integrity: sha512-EanJqcU/4uZIBreTrnbnre2DXgXSa+Gjap7ifRfllpmyAU7YMvaXmljdArptTHmjrkkKm9BK6GH5D5Yo+p6y5A==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.21.5': - resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - '@esbuild/netbsd-x64@0.24.0': resolution: {integrity: sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==} engines: {node: '>=18'} @@ -686,90 +526,30 @@ packages: cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.17.6': - resolution: {integrity: sha512-xaxeSunhQRsTNGFanoOkkLtnmMn5QbA0qBhNet/XLVsc+OVkpIWPHcr3zTW2gxVU5YOHFbIHR9ODuaUdNza2Vw==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.21.5': - resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - '@esbuild/openbsd-x64@0.24.0': resolution: {integrity: sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/sunos-x64@0.17.6': - resolution: {integrity: sha512-gnMnMPg5pfMkZvhHee21KbKdc6W3GR8/JuE0Da1kjwpK6oiFU3nqfHuVPgUX2rsOx9N2SadSQTIYV1CIjYG+xw==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - - '@esbuild/sunos-x64@0.21.5': - resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - '@esbuild/sunos-x64@0.24.0': resolution: {integrity: sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.17.6': - resolution: {integrity: sha512-G95n7vP1UnGJPsVdKXllAJPtqjMvFYbN20e8RK8LVLhlTiSOH1sd7+Gt7rm70xiG+I5tM58nYgwWrLs6I1jHqg==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-arm64@0.21.5': - resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - '@esbuild/win32-arm64@0.24.0': resolution: {integrity: sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.17.6': - resolution: {integrity: sha512-96yEFzLhq5bv9jJo5JhTs1gI+1cKQ83cUpyxHuGqXVwQtY5Eq54ZEsKs8veKtiKwlrNimtckHEkj4mRh4pPjsg==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-ia32@0.21.5': - resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - '@esbuild/win32-ia32@0.24.0': resolution: {integrity: sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.17.6': - resolution: {integrity: sha512-n6d8MOyUrNp6G4VSpRcgjs5xj4A91svJSaiwLIDWVWEsZtpN5FA9NlBbZHDmAJc2e8e6SF4tkBD3HAvPF+7igA==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - - '@esbuild/win32-x64@0.21.5': - resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - '@esbuild/win32-x64@0.24.0': resolution: {integrity: sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==} engines: {node: '>=18'} @@ -841,9 +621,6 @@ packages: peerDependencies: jsep: ^0.4.0||^1.0.0 - '@jspm/core@2.0.1': - resolution: {integrity: sha512-Lg3PnLp0QXpxwLIAuuJboLeRaIhrgJjeuh797QADg3xz8wGLugQOS5DpsE8A6i6Adgzf+bacllkKZG3J0tGfDw==} - '@kubernetes/client-node@0.22.3': resolution: {integrity: sha512-dG8uah3+HDJLpJEESshLRZlAZ4PgDeV9mZXT0u1g7oy4KMRzdZ7n5g0JEIlL6QhK51/2ztcIqURAnjfjJt6Z+g==} @@ -856,8 +633,8 @@ packages: '@lezer/lr@1.4.2': resolution: {integrity: sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==} - '@mdx-js/mdx@2.3.0': - resolution: {integrity: sha512-jLuwRlz8DQfQNiUCJR50Y09CGPq3fLtmtUQfVrj79E0JWu3dvsVcxVIcfhR5h0iXu+/z++zDrYeiJqifRynJkA==} + '@mjackson/node-fetch-server@0.2.0': + resolution: {integrity: sha512-EMlH1e30yzmTpGLQjlFmaDAjyOeZhng1/XCd7DExR8PNAnG/G1tyruZxEoUe11ClnwGhGrtsdnyyUx1frSzjng==} '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} @@ -871,10 +648,6 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@npmcli/fs@3.1.1': - resolution: {integrity: sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - '@npmcli/git@4.1.0': resolution: {integrity: sha512-9hwoB3gStVfa0N31ymBmrX+GuDGdVA/QWShZVqE0HK2Af+7QGGrCTbZia/SW0ImUTjTne7SP91qxDmtXvDHRPQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -1158,6 +931,34 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + '@react-router/dev@7.1.1': + resolution: {integrity: sha512-+UCrQZBAmdRcC7Bx1ho89T/DeP+FzEErkzrTvdBCpstr8AzOQ6mKlaglXGty15o3fgihBSFF4/J67jGveYIR8Q==} + engines: {node: '>=20.0.0'} + hasBin: true + peerDependencies: + '@react-router/serve': ^7.1.1 + react-router: ^7.1.1 + typescript: ^5.1.0 + vite: ^5.1.0 || ^6.0.0 + wrangler: ^3.28.2 + peerDependenciesMeta: + '@react-router/serve': + optional: true + typescript: + optional: true + wrangler: + optional: true + + '@react-router/node@7.1.1': + resolution: {integrity: sha512-5X79SfJ1IEEsttt0oo9rhO9kgxXyBTKdVBsz3h0WHTkRzbRk0VEpVpBW3PQ1RpkgEaAHwJ8obVl4k4brdDSExA==} + engines: {node: '>=20.0.0'} + peerDependencies: + react-router: 7.1.1 + typescript: ^5.1.0 + peerDependenciesMeta: + typescript: + optional: true + '@react-stately/calendar@3.6.0': resolution: {integrity: sha512-GqUtOtGnwWjtNrJud8nY/ywI4VBP5byToNVRTnxbMl+gYO1Qe/uc5NG7zjwMxhb2kqSBHZFdkF0DXVqG2Ul+BA==} peerDependencies: @@ -1441,26 +1242,6 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@remix-run/dev@2.15.0': - resolution: {integrity: sha512-iXV6u9PBwFc7KriDpVcjqLGJzZZd6ZOrxewen7hoH0OBzGwjkhtm46BTQEJrZ/e/dzlU1IU/0ylH29tN9BZoyg==} - engines: {node: '>=18.0.0'} - hasBin: true - peerDependencies: - '@remix-run/react': ^2.15.0 - '@remix-run/serve': ^2.15.0 - typescript: ^5.1.0 - vite: ^5.1.0 - wrangler: ^3.28.2 - peerDependenciesMeta: - '@remix-run/serve': - optional: true - typescript: - optional: true - vite: - optional: true - wrangler: - optional: true - '@remix-run/node@2.15.0': resolution: {integrity: sha512-tWbR7pQ6gwj+MkGf6WVIYnjgfGfpdU8EOIa6xsCIRlrm0p3BtMz4jA3GvBWEpOuEnN5MV7CarVzhduaRzkZ0SQ==} engines: {node: '>=18.0.0'} @@ -1481,16 +1262,6 @@ packages: typescript: optional: true - '@remix-run/route-config@2.15.0': - resolution: {integrity: sha512-i1m17W5jpOXsH5NyZfudHk89qOom/67faYX31qI8Zzv8fgj1hM2NYGoExcpumhL2tvMKSpEW4oE+95adrCGpGw==} - engines: {node: '>=18.0.0'} - peerDependencies: - '@remix-run/dev': ^2.15.0 - typescript: ^5.1.0 - peerDependenciesMeta: - typescript: - optional: true - '@remix-run/router@1.21.0': resolution: {integrity: sha512-xfSkCAchbdG5PnbrKqFWwia4Bi61nH+wm8wLEqfHDyp7Y3dZzgqS2itV8i4gAq9pC2HsTpwyBC6Ds8VHZ96JlA==} engines: {node: '>=14.0.0'} @@ -1520,101 +1291,51 @@ packages: '@remix-run/web-stream@1.1.0': resolution: {integrity: sha512-KRJtwrjRV5Bb+pM7zxcTJkhIqWWSy+MYsIxHK+0m5atcznsf15YwUBWHWulZerV2+vvHH1Lp1DD7pw6qKW8SgA==} - '@rollup/rollup-android-arm-eabi@4.24.4': - resolution: {integrity: sha512-jfUJrFct/hTA0XDM5p/htWKoNNTbDLY0KRwEt6pyOA6k2fmk0WVwl65PdUdJZgzGEHWx+49LilkcSaumQRyNQw==} - cpu: [arm] - os: [android] - '@rollup/rollup-android-arm-eabi@4.28.1': resolution: {integrity: sha512-2aZp8AES04KI2dy3Ss6/MDjXbwBzj+i0GqKtWXgw2/Ma6E4jJvujryO6gJAghIRVz7Vwr9Gtl/8na3nDUKpraQ==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.24.4': - resolution: {integrity: sha512-j4nrEO6nHU1nZUuCfRKoCcvh7PIywQPUCBa2UsootTHvTHIoIu2BzueInGJhhvQO/2FTRdNYpf63xsgEqH9IhA==} - cpu: [arm64] - os: [android] - '@rollup/rollup-android-arm64@4.28.1': resolution: {integrity: sha512-EbkK285O+1YMrg57xVA+Dp0tDBRB93/BZKph9XhMjezf6F4TpYjaUSuPt5J0fZXlSag0LmZAsTmdGGqPp4pQFA==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.24.4': - resolution: {integrity: sha512-GmU/QgGtBTeraKyldC7cDVVvAJEOr3dFLKneez/n7BvX57UdhOqDsVwzU7UOnYA7AAOt+Xb26lk79PldDHgMIQ==} - cpu: [arm64] - os: [darwin] - '@rollup/rollup-darwin-arm64@4.28.1': resolution: {integrity: sha512-prduvrMKU6NzMq6nxzQw445zXgaDBbMQvmKSJaxpaZ5R1QDM8w+eGxo6Y/jhT/cLoCvnZI42oEqf9KQNYz1fqQ==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.24.4': - resolution: {integrity: sha512-N6oDBiZCBKlwYcsEPXGDE4g9RoxZLK6vT98M8111cW7VsVJFpNEqvJeIPfsCzbf0XEakPslh72X0gnlMi4Ddgg==} - cpu: [x64] - os: [darwin] - '@rollup/rollup-darwin-x64@4.28.1': resolution: {integrity: sha512-WsvbOunsUk0wccO/TV4o7IKgloJ942hVFK1CLatwv6TJspcCZb9umQkPdvB7FihmdxgaKR5JyxDjWpCOp4uZlQ==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.24.4': - resolution: {integrity: sha512-py5oNShCCjCyjWXCZNrRGRpjWsF0ic8f4ieBNra5buQz0O/U6mMXCpC1LvrHuhJsNPgRt36tSYMidGzZiJF6mw==} - cpu: [arm64] - os: [freebsd] - '@rollup/rollup-freebsd-arm64@4.28.1': resolution: {integrity: sha512-HTDPdY1caUcU4qK23FeeGxCdJF64cKkqajU0iBnTVxS8F7H/7BewvYoG+va1KPSL63kQ1PGNyiwKOfReavzvNA==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.24.4': - resolution: {integrity: sha512-L7VVVW9FCnTTp4i7KrmHeDsDvjB4++KOBENYtNYAiYl96jeBThFfhP6HVxL74v4SiZEVDH/1ILscR5U9S4ms4g==} - cpu: [x64] - os: [freebsd] - '@rollup/rollup-freebsd-x64@4.28.1': resolution: {integrity: sha512-m/uYasxkUevcFTeRSM9TeLyPe2QDuqtjkeoTpP9SW0XxUWfcYrGDMkO/m2tTw+4NMAF9P2fU3Mw4ahNvo7QmsQ==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.24.4': - resolution: {integrity: sha512-10ICosOwYChROdQoQo589N5idQIisxjaFE/PAnX2i0Zr84mY0k9zul1ArH0rnJ/fpgiqfu13TFZR5A5YJLOYZA==} - cpu: [arm] - os: [linux] - '@rollup/rollup-linux-arm-gnueabihf@4.28.1': resolution: {integrity: sha512-QAg11ZIt6mcmzpNE6JZBpKfJaKkqTm1A9+y9O+frdZJEuhQxiugM05gnCWiANHj4RmbgeVJpTdmKRmH/a+0QbA==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.24.4': - resolution: {integrity: sha512-ySAfWs69LYC7QhRDZNKqNhz2UKN8LDfbKSMAEtoEI0jitwfAG2iZwVqGACJT+kfYvvz3/JgsLlcBP+WWoKCLcw==} - cpu: [arm] - os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.28.1': resolution: {integrity: sha512-dRP9PEBfolq1dmMcFqbEPSd9VlRuVWEGSmbxVEfiq2cs2jlZAl0YNxFzAQS2OrQmsLBLAATDMb3Z6MFv5vOcXg==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.24.4': - resolution: {integrity: sha512-uHYJ0HNOI6pGEeZ/5mgm5arNVTI0nLlmrbdph+pGXpC9tFHFDQmDMOEqkmUObRfosJqpU8RliYoGz06qSdtcjg==} - cpu: [arm64] - os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.28.1': resolution: {integrity: sha512-uGr8khxO+CKT4XU8ZUH1TTEUtlktK6Kgtv0+6bIFSeiSlnGJHG1tSFSjm41uQ9sAO/5ULx9mWOz70jYLyv1QkA==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.24.4': - resolution: {integrity: sha512-38yiWLemQf7aLHDgTg85fh3hW9stJ0Muk7+s6tIkSUOMmi4Xbv5pH/5Bofnsb6spIwD5FJiR+jg71f0CH5OzoA==} - cpu: [arm64] - os: [linux] - '@rollup/rollup-linux-arm64-musl@4.28.1': resolution: {integrity: sha512-QF54q8MYGAqMLrX2t7tNpi01nvq5RI59UBNx+3+37zoKX5KViPo/gk2QLhsuqok05sSCRluj0D00LzCwBikb0A==} cpu: [arm64] @@ -1625,81 +1346,41 @@ packages: cpu: [loong64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.24.4': - resolution: {integrity: sha512-q73XUPnkwt9ZNF2xRS4fvneSuaHw2BXuV5rI4cw0fWYVIWIBeDZX7c7FWhFQPNTnE24172K30I+dViWRVD9TwA==} - cpu: [ppc64] - os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.28.1': resolution: {integrity: sha512-pTnTdBuC2+pt1Rmm2SV7JWRqzhYpEILML4PKODqLz+C7Ou2apEV52h19CR7es+u04KlqplggmN9sqZlekg3R1A==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.24.4': - resolution: {integrity: sha512-Aie/TbmQi6UXokJqDZdmTJuZBCU3QBDA8oTKRGtd4ABi/nHgXICulfg1KI6n9/koDsiDbvHAiQO3YAUNa/7BCw==} - cpu: [riscv64] - os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.28.1': resolution: {integrity: sha512-vWXy1Nfg7TPBSuAncfInmAI/WZDd5vOklyLJDdIRKABcZWojNDY0NJwruY2AcnCLnRJKSaBgf/GiJfauu8cQZA==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.24.4': - resolution: {integrity: sha512-P8MPErVO/y8ohWSP9JY7lLQ8+YMHfTI4bAdtCi3pC2hTeqFJco2jYspzOzTUB8hwUWIIu1xwOrJE11nP+0JFAQ==} - cpu: [s390x] - os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.28.1': resolution: {integrity: sha512-/yqC2Y53oZjb0yz8PVuGOQQNOTwxcizudunl/tFs1aLvObTclTwZ0JhXF2XcPT/zuaymemCDSuuUPXJJyqeDOg==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.24.4': - resolution: {integrity: sha512-K03TljaaoPK5FOyNMZAAEmhlyO49LaE4qCsr0lYHUKyb6QacTNF9pnfPpXnFlFD3TXuFbFbz7tJ51FujUXkXYA==} - cpu: [x64] - os: [linux] - '@rollup/rollup-linux-x64-gnu@4.28.1': resolution: {integrity: sha512-fzgeABz7rrAlKYB0y2kSEiURrI0691CSL0+KXwKwhxvj92VULEDQLpBYLHpF49MSiPG4sq5CK3qHMnb9tlCjBw==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.24.4': - resolution: {integrity: sha512-VJYl4xSl/wqG2D5xTYncVWW+26ICV4wubwN9Gs5NrqhJtayikwCXzPL8GDsLnaLU3WwhQ8W02IinYSFJfyo34Q==} - cpu: [x64] - os: [linux] - '@rollup/rollup-linux-x64-musl@4.28.1': resolution: {integrity: sha512-xQTDVzSGiMlSshpJCtudbWyRfLaNiVPXt1WgdWTwWz9n0U12cI2ZVtWe/Jgwyv/6wjL7b66uu61Vg0POWVfz4g==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.24.4': - resolution: {integrity: sha512-ku2GvtPwQfCqoPFIJCqZ8o7bJcj+Y54cZSr43hHca6jLwAiCbZdBUOrqE6y29QFajNAzzpIOwsckaTFmN6/8TA==} - cpu: [arm64] - os: [win32] - '@rollup/rollup-win32-arm64-msvc@4.28.1': resolution: {integrity: sha512-wSXmDRVupJstFP7elGMgv+2HqXelQhuNf+IS4V+nUpNVi/GUiBgDmfwD0UGN3pcAnWsgKG3I52wMOBnk1VHr/A==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.24.4': - resolution: {integrity: sha512-V3nCe+eTt/W6UYNr/wGvO1fLpHUrnlirlypZfKCT1fG6hWfqhPgQV/K/mRBXBpxc0eKLIF18pIOFVPh0mqHjlg==} - cpu: [ia32] - os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.28.1': resolution: {integrity: sha512-ZkyTJ/9vkgrE/Rk9vhMXhf8l9D+eAhbAVbsGsXKy2ohmJaWg0LPQLnIxRdRp/bKyr8tXuPlXhIoGlEB5XpJnGA==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.24.4': - resolution: {integrity: sha512-LTw1Dfd0mBIEqUVCxbvTE/LLo+9ZxVC9k99v1v4ahg9Aak6FpqOfNu5kRkeTAn0wphoC4JU7No1/rL+bBCEwhg==} - cpu: [x64] - os: [win32] - '@rollup/rollup-win32-x64-msvc@4.28.1': resolution: {integrity: sha512-ZvK2jBafvttJjoIdKm/Q/Bh7IJ1Ose9IBOwpOXcOvW3ikGTQGmKDgxTC6oCAzW6PynbkKP8+um1du81XJHZ0JA==} cpu: [x64] @@ -1711,33 +1392,12 @@ packages: '@swc/helpers@0.5.15': resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} - '@types/acorn@4.0.6': - resolution: {integrity: sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==} - '@types/cookie@0.6.0': resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} - '@types/debug@4.1.12': - resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} - - '@types/estree-jsx@1.0.5': - resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} - '@types/estree@1.0.6': resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} - '@types/hast@2.3.10': - resolution: {integrity: sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==} - - '@types/mdast@3.0.15': - resolution: {integrity: sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==} - - '@types/mdx@2.0.13': - resolution: {integrity: sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==} - - '@types/ms@0.7.34': - resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} - '@types/node@22.10.1': resolution: {integrity: sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==} @@ -1747,9 +1407,6 @@ packages: '@types/react@19.0.1': resolution: {integrity: sha512-YW6614BDhqbpR5KtUYzTA+zlA7nayzJRA9ljz9CQoxthR0sDisYZLuvSMsil36t4EH/uAt8T52Xb4sVw17G+SQ==} - '@types/unist@2.0.11': - resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} - '@uiw/codemirror-extensions-basic-setup@4.23.6': resolution: {integrity: sha512-bvtq8IOvdkLJMhoJBRGPEzU51fMpPDwEhcAHp9xCR05MtbIokQgsnLXrmD1aZm6e7s/3q47H+qdSfAAkR5MkLA==} peerDependencies: @@ -1782,18 +1439,6 @@ packages: react: '>=16.8.0' react-dom: '>=16.8.0' - '@vanilla-extract/babel-plugin-debug-ids@1.1.0': - resolution: {integrity: sha512-Zy9bKjaL2P5zsrFYQJ8IjWGlFODmZrpvFmjFE0Zv8om55Pz1JtpJtL6DvlxlWUxbVaP1HKCqsmEfFOZN8fX/ZQ==} - - '@vanilla-extract/css@1.16.1': - resolution: {integrity: sha512-3jKxH5ty/ZjmGoLAx8liY7e87FRCIJfnuufX/K9fQklu0YHP3ClrNisU++LkZuD+GZleqMSAQMF0r8Otln+OPQ==} - - '@vanilla-extract/integration@6.5.0': - resolution: {integrity: sha512-E2YcfO8vA+vs+ua+gpvy1HRqvgWbI+MTlUpxA8FvatOvybuNcWAY0CKwQ/Gpj7rswYKtC6C7+xw33emM6/ImdQ==} - - '@vanilla-extract/private@1.0.6': - resolution: {integrity: sha512-ytsG/JLweEjw7DBuZ/0JCN4WAQgM9erfSTdS1NQY778hFQSZ6cfCDEZZ0sgVm4k54uNz6ImKB33AYvSR//fjxw==} - '@web3-storage/multipart-parser@1.0.0': resolution: {integrity: sha512-BEO6al7BYqcnfX15W2cnGR+Q566ACXAT9UQykORCWW80lmkpWsnEob6zJS1ZVBKsSJC8+7vJkHwlp+lXG1UCdw==} @@ -1804,24 +1449,6 @@ packages: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} engines: {node: '>=6.5'} - accepts@1.3.8: - resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} - engines: {node: '>= 0.6'} - - acorn-jsx@5.3.2: - resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - - acorn@8.14.0: - resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} - engines: {node: '>=0.4.0'} - hasBin: true - - aggregate-error@3.1.0: - resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} - engines: {node: '>=8'} - ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} @@ -1854,9 +1481,6 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - array-flatten@1.1.1: - resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} - asn1@0.2.6: resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} @@ -1864,10 +1488,6 @@ packages: resolution: {integrity: sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==} engines: {node: '>=0.8'} - astring@1.9.0: - resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} - hasBin: true - asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} @@ -1888,18 +1508,15 @@ packages: aws4@1.13.2: resolution: {integrity: sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==} + babel-dead-code-elimination@1.0.8: + resolution: {integrity: sha512-og6HQERk0Cmm+nTT4Od2wbPtgABXFMPaHACjbKLulZIFMkYyXZLkUGuAxdgpMJBrxyt/XFpSz++lNzjbcMnPkQ==} + babel-plugin-react-compiler@19.0.0-beta-df7b47d-20241124: resolution: {integrity: sha512-93iSASR20HNsotcOTQ+KPL0zpgfRFVWL86AtXpmHp995HuMVnC9femd8Winr3GxkPEh8lEOyaw3nqY4q2HUm5w==} - bail@2.0.2: - resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} - balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - bcrypt-pbkdf@1.0.2: resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==} @@ -1907,13 +1524,6 @@ packages: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} - bl@4.1.0: - resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} - - body-parser@1.20.3: - resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} - engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - brace-expansion@2.0.1: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} @@ -1932,25 +1542,14 @@ packages: buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - buffer@5.7.1: - resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} - byline@5.0.0: resolution: {integrity: sha512-s6webAy+R4SR8XVuJWt2V2rGvhnrhxN+9S15GNuTK3wKPOXFF6RNc+8ug2XhH+2s4f+uudG4kUVYmYOQWL2g0Q==} engines: {node: '>=0.10.0'} - bytes@3.1.2: - resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} - engines: {node: '>= 0.8'} - cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} - cacache@17.1.4: - resolution: {integrity: sha512-/aJwG2l3ZMJ1xNAnqbMpA40of9dj/pIH3QfiuQSqjfPJF747VR0J/bHn+/KdNnHKc6XQcWt/AfRSBft82W1d2A==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - call-bind-apply-helpers@1.0.0: resolution: {integrity: sha512-CCKAP2tkPau7D3GE8+V8R6sQubA9R5foIzGp+85EXCVSCivuxBNAWqcpn72PKYiIcqoViv/kcUDpaEIMBVi1lQ==} engines: {node: '>= 0.4'} @@ -1969,59 +1568,21 @@ packages: caseless@0.12.0: resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} - ccount@2.0.1: - resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} - - chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} - - character-entities-html4@2.1.0: - resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} - - character-entities-legacy@3.0.0: - resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} - - character-entities@2.0.2: - resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} - - character-reference-invalid@2.0.1: - resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} - chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} - chownr@1.1.4: - resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} - - chownr@2.0.0: - resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} - engines: {node: '>=10'} + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} chownr@3.0.0: resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} engines: {node: '>=18'} - clean-stack@2.2.0: - resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} - engines: {node: '>=6'} - - cli-cursor@3.1.0: - resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} - engines: {node: '>=8'} - - cli-spinners@2.9.2: - resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} - engines: {node: '>=6'} - client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} - clone@1.0.4: - resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} - engines: {node: '>=0.8'} - clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} @@ -2040,30 +1601,13 @@ packages: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} - comma-separated-tokens@2.0.3: - resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} - commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} - confbox@0.1.8: - resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} - - content-disposition@0.5.4: - resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} - engines: {node: '>= 0.6'} - - content-type@1.0.5: - resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} - engines: {node: '>= 0.6'} - convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - cookie-signature@1.0.6: - resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} - cookie-signature@1.2.2: resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} engines: {node: '>=6.6.0'} @@ -2072,9 +1616,9 @@ packages: resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} engines: {node: '>= 0.6'} - cookie@0.7.1: - resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} - engines: {node: '>= 0.6'} + cookie@1.0.2: + resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} + engines: {node: '>=18'} core-util-is@1.0.2: resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==} @@ -2089,14 +1633,6 @@ packages: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} - cross-spawn@7.0.6: - resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} - engines: {node: '>= 8'} - - css-what@6.1.0: - resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} - engines: {node: '>= 6'} - cssesc@3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} engines: {node: '>=4'} @@ -2113,14 +1649,6 @@ packages: resolution: {integrity: sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og==} engines: {node: '>= 6'} - debug@2.6.9: - resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - debug@4.4.0: resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} engines: {node: '>=6.0'} @@ -2130,9 +1658,6 @@ packages: supports-color: optional: true - decode-named-character-reference@1.0.2: - resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==} - dedent@1.5.3: resolution: {integrity: sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==} peerDependencies: @@ -2141,16 +1666,6 @@ packages: babel-plugin-macros: optional: true - deep-object-diff@1.1.9: - resolution: {integrity: sha512-Rn+RuwkmkDwCi2/oXOFS9Gsr5lJZu/yTGpK7wAaAIE75CC+LCGEZHpY6VQJa/RoJcrmaA/docWJZvYohlNkWPA==} - - deepmerge@4.3.1: - resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} - engines: {node: '>=0.10.0'} - - defaults@1.0.4: - resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} - define-data-property@1.1.4: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} @@ -2159,25 +1674,9 @@ packages: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} - depd@2.0.0: - resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} - engines: {node: '>= 0.8'} - - dequal@2.0.3: - resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} - engines: {node: '>=6'} - - destroy@1.2.0: - resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} - engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} - diff@5.2.0: - resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==} - engines: {node: '>=0.3.1'} - dlv@1.1.3: resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} @@ -2194,9 +1693,6 @@ packages: ecc-jsbn@0.1.2: resolution: {integrity: sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==} - ee-first@1.1.1: - resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - electron-to-chromium@1.5.51: resolution: {integrity: sha512-kKeWV57KSS8jH4alKt/jKnvHPmJgBxXzGUSbMd4eQF+iOsVPl7bz2KUmu6eo80eMP8wVioTfTyTzdMgM15WXNg==} @@ -2206,14 +1702,6 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - encodeurl@1.0.2: - resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} - engines: {node: '>= 0.8'} - - encodeurl@2.0.0: - resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} - engines: {node: '>= 0.8'} - end-of-stream@1.4.4: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} @@ -2231,22 +1719,6 @@ packages: es-module-lexer@1.5.4: resolution: {integrity: sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==} - esbuild-plugins-node-modules-polyfill@1.6.8: - resolution: {integrity: sha512-bRB4qbgUDWrdY1eMk123KiaCSW9VzQ+QLZrmU7D//cCFkmksPd9mUMpmWoFK/rxjIeTfTSOpKCoGoimlvI+AWw==} - engines: {node: '>=14.0.0'} - peerDependencies: - esbuild: '>=0.14.0 <=0.24.x' - - esbuild@0.17.6: - resolution: {integrity: sha512-TKFRp9TxrJDdRWfSsSERKEovm6v30iHnrjlcGhLBOtReE28Yp1VSBRfO3GTaOFMoxsNerx4TjrhzSuma9ha83Q==} - engines: {node: '>=12'} - hasBin: true - - esbuild@0.21.5: - resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} - engines: {node: '>=12'} - hasBin: true - esbuild@0.24.0: resolution: {integrity: sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==} engines: {node: '>=18'} @@ -2256,58 +1728,14 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} - escape-html@1.0.3: - resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} - - estree-util-attach-comments@2.1.1: - resolution: {integrity: sha512-+5Ba/xGGS6mnwFbXIuQiDPTbuTxuMCooq3arVv7gPZtYpjp+VXH/NkHAP35OOefPhNG/UGqU3vt/LTABwcHX0w==} - - estree-util-build-jsx@2.2.2: - resolution: {integrity: sha512-m56vOXcOBuaF+Igpb9OPAy7f9w9OIkb5yhjsZuaPm7HoGi4oTOQi0h2+yZ+AtKklYFZ+rPC4n0wYCJCEU1ONqg==} - - estree-util-is-identifier-name@1.1.0: - resolution: {integrity: sha512-OVJZ3fGGt9By77Ix9NhaRbzfbDV/2rx9EP7YIDJTmsZSEc5kYn2vWcNccYyahJL2uAQZK2a5Or2i0wtIKTPoRQ==} - - estree-util-is-identifier-name@2.1.0: - resolution: {integrity: sha512-bEN9VHRyXAUOjkKVQVvArFym08BTWB0aJPppZZr0UNyAqWsLaVfAqP7hbaTJjzHifmB5ebnR8Wm7r7yGN/HonQ==} - - estree-util-to-js@1.2.0: - resolution: {integrity: sha512-IzU74r1PK5IMMGZXUVZbmiu4A1uhiPgW5hm1GjcOfr4ZzHaMPpLNJjR7HjXiIOzi25nZDrgFTobHTkV5Q6ITjA==} - - estree-util-value-to-estree@1.3.0: - resolution: {integrity: sha512-Y+ughcF9jSUJvncXwqRageavjrNPAI+1M/L3BI3PyLp1nmgYTGUXU6t5z1Y7OWuThoDdhPME07bQU+d5LxdJqw==} - engines: {node: '>=12.0.0'} - - estree-util-visit@1.2.1: - resolution: {integrity: sha512-xbgqcrkIVbIG+lI/gzbvd9SGTJL4zqJKBFttUl5pP27KhAjtMKbX/mQXJ7qgyXpMgVy/zvpm0xoQQaGL8OloOw==} - - estree-walker@3.0.3: - resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} - - etag@1.8.1: - resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} - engines: {node: '>= 0.6'} - - eval@0.1.8: - resolution: {integrity: sha512-EzV94NYKoO09GLXGjXj9JIlXijVck4ONSr5wiCWDvhsvj5jxSrzTmRU/9C1DyB6uToszLs8aifA6NQ7lEQdvFw==} - engines: {node: '>= 0.8'} - event-target-shim@5.0.1: resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} engines: {node: '>=6'} - execa@5.1.1: - resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} - engines: {node: '>=10'} - exit-hook@2.2.1: resolution: {integrity: sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==} engines: {node: '>=6'} - express@4.21.2: - resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==} - engines: {node: '>= 0.10.0'} - extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} @@ -2328,21 +1756,10 @@ packages: fastq@1.17.1: resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} - fault@2.0.1: - resolution: {integrity: sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==} - fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} - finalhandler@1.3.1: - resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==} - engines: {node: '>= 0.8'} - - find-up@5.0.0: - resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} - engines: {node: '>=10'} - for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} @@ -2357,36 +1774,13 @@ packages: resolution: {integrity: sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==} engines: {node: '>= 0.12'} - format@0.2.2: - resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} - engines: {node: '>=0.4.x'} - - forwarded@0.2.0: - resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} - engines: {node: '>= 0.6'} - fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} - fresh@0.5.2: - resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} - engines: {node: '>= 0.6'} - - fs-constants@1.0.0: - resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} - fs-extra@10.1.0: resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} engines: {node: '>=12'} - fs-minipass@2.1.0: - resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} - engines: {node: '>= 8'} - - fs-minipass@3.0.3: - resolution: {integrity: sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -2395,9 +1789,6 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - generic-names@4.0.0: - resolution: {integrity: sha512-ySFolZQfw9FoDb3ed9d80Cm9f0+r7qj+HJkWjeD9RBfpxEVTlVhol+gvaQB/78WbwYfbnNh8nWHHBSlg072y6A==} - gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -2406,14 +1797,6 @@ packages: resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} engines: {node: '>= 0.4'} - get-port@5.1.1: - resolution: {integrity: sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==} - engines: {node: '>=8'} - - get-stream@6.0.1: - resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} - engines: {node: '>=10'} - getpass@0.1.7: resolution: {integrity: sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==} @@ -2436,9 +1819,6 @@ packages: globrex@0.1.2: resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} - gopd@1.0.1: - resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} - gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} @@ -2459,10 +1839,6 @@ packages: engines: {node: '>=6'} deprecated: this library is no longer supported - has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} - has-property-descriptors@1.0.2: resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} @@ -2470,10 +1846,6 @@ packages: resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==} engines: {node: '>= 0.4'} - has-symbols@1.0.3: - resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} - engines: {node: '>= 0.4'} - has-symbols@1.1.0: resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} engines: {node: '>= 0.4'} @@ -2486,68 +1858,20 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} - hast-util-to-estree@2.3.3: - resolution: {integrity: sha512-ihhPIUPxN0v0w6M5+IiAZZrn0LH2uZomeWwhn7uP7avZC6TE7lIiEh2yBMPr5+zi1aUCXq6VoYRgs2Bw9xmycQ==} - - hast-util-whitespace@2.0.1: - resolution: {integrity: sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==} - hosted-git-info@6.1.3: resolution: {integrity: sha512-HVJyzUrLIL1c0QmviVh5E8VGyUS7xCFPS6yydaVd1UegW+ibV/CohqTH9MkOLDp5o+rb82DMo77PTuc9F/8GKw==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - http-errors@2.0.0: - resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} - engines: {node: '>= 0.8'} - http-signature@1.2.0: resolution: {integrity: sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==} engines: {node: '>=0.8', npm: '>=1.3.7'} - human-signals@2.1.0: - resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} - engines: {node: '>=10.17.0'} - - iconv-lite@0.4.24: - resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} - engines: {node: '>=0.10.0'} - - icss-utils@5.1.0: - resolution: {integrity: sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==} - engines: {node: ^10 || ^12 || >= 14} - peerDependencies: - postcss: ^8.1.0 - - ieee754@1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - - imurmurhash@0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} - engines: {node: '>=0.8.19'} - - indent-string@4.0.0: - resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} - engines: {node: '>=8'} - inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - inline-style-parser@0.1.1: - resolution: {integrity: sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==} - intl-messageformat@10.7.7: resolution: {integrity: sha512-F134jIoeYMro/3I0h08D0Yt4N9o9pjddU/4IIxMMURqbAtI2wu70X8hvG1V48W49zXHXv3RKSF/po+0fDfsGjA==} - ipaddr.js@1.9.1: - resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} - engines: {node: '>= 0.10'} - - is-alphabetical@2.0.1: - resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} - - is-alphanumerical@2.0.1: - resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} - is-arguments@1.1.1: resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} engines: {node: '>= 0.4'} @@ -2556,10 +1880,6 @@ packages: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} - is-buffer@2.0.5: - resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==} - engines: {node: '>=4'} - is-callable@1.2.7: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} engines: {node: '>= 0.4'} @@ -2568,9 +1888,6 @@ packages: resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==} engines: {node: '>= 0.4'} - is-decimal@2.0.1: - resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} - is-deflate@1.0.0: resolution: {integrity: sha512-YDoFpuZWu1VRXlsnlYMzKyVRITXj7Ej/V9gXQ2/pAe7X1J7M/RNOqaIYi6qUn+B7nGyB9pDXrv02dsB58d2ZAQ==} @@ -2594,32 +1911,10 @@ packages: resolution: {integrity: sha512-rcfALRIb1YewtnksfRIHGcIY93QnK8BIQ/2c9yDYcG/Y6+vRoJuTWBmmSEbyLLYtXm7q35pHOHbZFQBaLrhlWQ==} engines: {node: '>=0.10.0'} - is-hexadecimal@2.0.1: - resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} - - is-interactive@1.0.0: - resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} - engines: {node: '>=8'} - is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} - is-plain-obj@3.0.0: - resolution: {integrity: sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==} - engines: {node: '>=10'} - - is-plain-obj@4.1.0: - resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} - engines: {node: '>=12'} - - is-reference@3.0.3: - resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==} - - is-stream@2.0.1: - resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} - engines: {node: '>=8'} - is-typed-array@1.1.13: resolution: {integrity: sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==} engines: {node: '>= 0.4'} @@ -2627,10 +1922,6 @@ packages: is-typedarray@1.0.0: resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} - is-unicode-supported@0.1.0: - resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} - engines: {node: '>=10'} - isarray@1.0.0: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} @@ -2652,9 +1943,6 @@ packages: jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - javascript-stringify@2.1.0: - resolution: {integrity: sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg==} - jiti@1.21.6: resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==} hasBin: true @@ -2711,10 +1999,6 @@ packages: resolution: {integrity: sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==} engines: {node: '>=0.6.0'} - kleur@4.1.5: - resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} - engines: {node: '>=6'} - lilconfig@3.1.1: resolution: {integrity: sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==} engines: {node: '>=14'} @@ -2726,41 +2010,16 @@ packages: lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - loader-utils@3.3.1: - resolution: {integrity: sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg==} - engines: {node: '>= 12.13.0'} - - local-pkg@0.5.1: - resolution: {integrity: sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==} - engines: {node: '>=14'} - - locate-path@6.0.0: - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} - engines: {node: '>=10'} - - lodash.camelcase@4.3.0: - resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} - lodash.debounce@4.0.8: resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - log-symbols@4.1.0: - resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} - engines: {node: '>=10'} - - longest-streak@3.1.0: - resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} - lru-cache@10.2.2: resolution: {integrity: sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==} engines: {node: 14 || >=16.14} - lru-cache@10.4.3: - resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -2768,151 +2027,10 @@ packages: resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} engines: {node: '>=12'} - markdown-extensions@1.1.1: - resolution: {integrity: sha512-WWC0ZuMzCyDHYCasEGs4IPvLyTGftYwh6wIEOULOF0HXcqZlhwRzrK0w2VUlxWA98xnvb/jszw4ZSkJ6ADpM6Q==} - engines: {node: '>=0.10.0'} - - mdast-util-definitions@5.1.2: - resolution: {integrity: sha512-8SVPMuHqlPME/z3gqVwWY4zVXn8lqKv/pAhC57FuJ40ImXyBpmO5ukh98zB2v7Blql2FiHjHv9LVztSIqjY+MA==} - - mdast-util-from-markdown@1.3.1: - resolution: {integrity: sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==} - - mdast-util-frontmatter@1.0.1: - resolution: {integrity: sha512-JjA2OjxRqAa8wEG8hloD0uTU0kdn8kbtOWpPP94NBkfAlbxn4S8gCGf/9DwFtEeGPXrDcNXdiDjVaRdUFqYokw==} - - mdast-util-mdx-expression@1.3.2: - resolution: {integrity: sha512-xIPmR5ReJDu/DHH1OoIT1HkuybIfRGYRywC+gJtI7qHjCJp/M9jrmBEJW22O8lskDWm562BX2W8TiAwRTb0rKA==} - - mdast-util-mdx-jsx@2.1.4: - resolution: {integrity: sha512-DtMn9CmVhVzZx3f+optVDF8yFgQVt7FghCRNdlIaS3X5Bnym3hZwPbg/XW86vdpKjlc1PVj26SpnLGeJBXD3JA==} - - mdast-util-mdx@2.0.1: - resolution: {integrity: sha512-38w5y+r8nyKlGvNjSEqWrhG0w5PmnRA+wnBvm+ulYCct7nsGYhFVb0lljS9bQav4psDAS1eGkP2LMVcZBi/aqw==} - - mdast-util-mdxjs-esm@1.3.1: - resolution: {integrity: sha512-SXqglS0HrEvSdUEfoXFtcg7DRl7S2cwOXc7jkuusG472Mmjag34DUDeOJUZtl+BVnyeO1frIgVpHlNRWc2gk/w==} - - mdast-util-phrasing@3.0.1: - resolution: {integrity: sha512-WmI1gTXUBJo4/ZmSk79Wcb2HcjPJBzM1nlI/OUWA8yk2X9ik3ffNbBGsU+09BFmXaL1IBb9fiuvq6/KMiNycSg==} - - mdast-util-to-hast@12.3.0: - resolution: {integrity: sha512-pits93r8PhnIoU4Vy9bjW39M2jJ6/tdHyja9rrot9uujkN7UTU9SDnE6WNJz/IGyQk3XHX6yNNtrBH6cQzm8Hw==} - - mdast-util-to-markdown@1.5.0: - resolution: {integrity: sha512-bbv7TPv/WC49thZPg3jXuqzuvI45IL2EVAr/KxF0BSdHsU0ceFHOmwQn6evxAh1GaoK/6GQ1wp4R4oW2+LFL/A==} - - mdast-util-to-string@3.2.0: - resolution: {integrity: sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==} - - media-query-parser@2.0.2: - resolution: {integrity: sha512-1N4qp+jE0pL5Xv4uEcwVUhIkwdUO3S/9gML90nqKA7v7FcOS5vUtatfzok9S9U1EJU8dHWlcv95WLnKmmxZI9w==} - - media-typer@0.3.0: - resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} - engines: {node: '>= 0.6'} - - merge-descriptors@1.0.3: - resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} - - merge-stream@2.0.0: - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} - methods@1.1.2: - resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} - engines: {node: '>= 0.6'} - - micromark-core-commonmark@1.1.0: - resolution: {integrity: sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==} - - micromark-extension-frontmatter@1.1.1: - resolution: {integrity: sha512-m2UH9a7n3W8VAH9JO9y01APpPKmNNNs71P0RbknEmYSaZU5Ghogv38BYO94AI5Xw6OYfxZRdHZZ2nYjs/Z+SZQ==} - - micromark-extension-mdx-expression@1.0.8: - resolution: {integrity: sha512-zZpeQtc5wfWKdzDsHRBY003H2Smg+PUi2REhqgIhdzAa5xonhP03FcXxqFSerFiNUr5AWmHpaNPQTBVOS4lrXw==} - - micromark-extension-mdx-jsx@1.0.5: - resolution: {integrity: sha512-gPH+9ZdmDflbu19Xkb8+gheqEDqkSpdCEubQyxuz/Hn8DOXiXvrXeikOoBA71+e8Pfi0/UYmU3wW3H58kr7akA==} - - micromark-extension-mdx-md@1.0.1: - resolution: {integrity: sha512-7MSuj2S7xjOQXAjjkbjBsHkMtb+mDGVW6uI2dBL9snOBCbZmoNgDAeZ0nSn9j3T42UE/g2xVNMn18PJxZvkBEA==} - - micromark-extension-mdxjs-esm@1.0.5: - resolution: {integrity: sha512-xNRBw4aoURcyz/S69B19WnZAkWJMxHMT5hE36GtDAyhoyn/8TuAeqjFJQlwk+MKQsUD7b3l7kFX+vlfVWgcX1w==} - - micromark-extension-mdxjs@1.0.1: - resolution: {integrity: sha512-7YA7hF6i5eKOfFUzZ+0z6avRG52GpWR8DL+kN47y3f2KhxbBZMhmxe7auOeaTBrW2DenbbZTf1ea9tA2hDpC2Q==} - - micromark-factory-destination@1.1.0: - resolution: {integrity: sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==} - - micromark-factory-label@1.1.0: - resolution: {integrity: sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==} - - micromark-factory-mdx-expression@1.0.9: - resolution: {integrity: sha512-jGIWzSmNfdnkJq05c7b0+Wv0Kfz3NJ3N4cBjnbO4zjXIlxJr+f8lk+5ZmwFvqdAbUy2q6B5rCY//g0QAAaXDWA==} - - micromark-factory-space@1.1.0: - resolution: {integrity: sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==} - - micromark-factory-title@1.1.0: - resolution: {integrity: sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==} - - micromark-factory-whitespace@1.1.0: - resolution: {integrity: sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==} - - micromark-util-character@1.2.0: - resolution: {integrity: sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==} - - micromark-util-chunked@1.1.0: - resolution: {integrity: sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==} - - micromark-util-classify-character@1.1.0: - resolution: {integrity: sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==} - - micromark-util-combine-extensions@1.1.0: - resolution: {integrity: sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==} - - micromark-util-decode-numeric-character-reference@1.1.0: - resolution: {integrity: sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==} - - micromark-util-decode-string@1.1.0: - resolution: {integrity: sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==} - - micromark-util-encode@1.1.0: - resolution: {integrity: sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==} - - micromark-util-events-to-acorn@1.2.3: - resolution: {integrity: sha512-ij4X7Wuc4fED6UoLWkmo0xJQhsktfNh1J0m8g4PbIMPlx+ek/4YdW5mvbye8z/aZvAPUoxgXHrwVlXAPKMRp1w==} - - micromark-util-html-tag-name@1.2.0: - resolution: {integrity: sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==} - - micromark-util-normalize-identifier@1.1.0: - resolution: {integrity: sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==} - - micromark-util-resolve-all@1.1.0: - resolution: {integrity: sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==} - - micromark-util-sanitize-uri@1.2.0: - resolution: {integrity: sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==} - - micromark-util-subtokenize@1.1.0: - resolution: {integrity: sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==} - - micromark-util-symbol@1.1.0: - resolution: {integrity: sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==} - - micromark-util-types@1.1.0: - resolution: {integrity: sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==} - - micromark@3.2.0: - resolution: {integrity: sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==} - micromatch@4.0.8: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} @@ -2925,89 +2043,32 @@ packages: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} - mime@1.6.0: - resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} - engines: {node: '>=4'} - hasBin: true - mime@4.0.4: resolution: {integrity: sha512-v8yqInVjhXyqP6+Kw4fV3ZzeMRqEW6FotRsKXjRS5VMTNIuXsdRoAvklpoRgSqXm6o9VNH4/C0mgedko9DdLsQ==} engines: {node: '>=16'} hasBin: true - mimic-fn@2.1.0: - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} - engines: {node: '>=6'} - minimatch@9.0.5: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} - minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - - minipass-collect@1.0.2: - resolution: {integrity: sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==} - engines: {node: '>= 8'} - - minipass-flush@1.0.5: - resolution: {integrity: sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==} - engines: {node: '>= 8'} - - minipass-pipeline@1.2.4: - resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==} - engines: {node: '>=8'} - - minipass@3.3.6: - resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} - engines: {node: '>=8'} - - minipass@5.0.0: - resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} - engines: {node: '>=8'} - minipass@7.1.2: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} - minizlib@2.1.2: - resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} - engines: {node: '>= 8'} - minizlib@3.0.1: resolution: {integrity: sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg==} engines: {node: '>= 18'} - mkdirp-classic@0.5.3: - resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} - - mkdirp@1.0.4: - resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} - engines: {node: '>=10'} - hasBin: true - mkdirp@3.0.1: resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} engines: {node: '>=10'} hasBin: true - mlly@1.7.3: - resolution: {integrity: sha512-xUsx5n/mN0uQf4V548PKQ+YShA4/IW0KI1dZhrNrPCLG+xizETbHTkOa1f8/xut9JRPp8kQuMnz0oqwkTiLo/A==} - - modern-ahocorasick@1.1.0: - resolution: {integrity: sha512-sEKPVl2rM+MNVkGQt3ChdmD8YsigmXdn5NifZn6jiwn9LRJpWm8F3guhaqrJT/JOat6pwpbXEk6kv+b9DMIjsQ==} - - mri@1.2.0: - resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} - engines: {node: '>=4'} - mrmime@1.0.1: resolution: {integrity: sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==} engines: {node: '>=10'} - ms@2.0.0: - resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} - ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -3019,10 +2080,6 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - negotiator@0.6.3: - resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} - engines: {node: '>= 0.6'} - node-releases@2.0.18: resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} @@ -3054,10 +2111,6 @@ packages: resolution: {integrity: sha512-1dKY+86/AIiq1tkKVD3l0WI+Gd3vkknVGAggsFeBkTvbhMQ1OND/LKkYv4JtXPKUJ8bOTCyLiqEg2P6QNdK+Gg==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - npm-run-path@4.0.1: - resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} - engines: {node: '>=8'} - oauth-sign@0.9.0: resolution: {integrity: sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==} @@ -3075,64 +2128,18 @@ packages: resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} engines: {node: '>= 6'} - object-inspect@1.13.3: - resolution: {integrity: sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==} - engines: {node: '>= 0.4'} - - on-finished@2.4.1: - resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} - engines: {node: '>= 0.8'} - once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - onetime@5.1.2: - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} - engines: {node: '>=6'} - openid-client@6.1.7: resolution: {integrity: sha512-JfY/KvQgOutmG2P+oVNKInE7zIh+im1MQOaO7g5CtNnTWMociA563WweiEMKfR9ry9XG3K2HGvj9wEqhCQkPMg==} - ora@5.4.1: - resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} - engines: {node: '>=10'} - - outdent@0.8.0: - resolution: {integrity: sha512-KiOAIsdpUTcAXuykya5fnVVT+/5uS0Q1mrkRHcF89tpieSmY33O/tmc54CqwA+bfhbtEfZUNLHaPUiB9X3jt1A==} - - p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} - - p-locate@5.0.0: - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} - engines: {node: '>=10'} - - p-map@4.0.0: - resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} - engines: {node: '>=10'} - package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} pako@0.2.9: resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==} - parse-entities@4.0.1: - resolution: {integrity: sha512-SWzvYcSJh4d/SGLIOQfZ/CoNv6BTlI6YEQ7Nj82oDVnRpwe/Z/F1EMx42x3JAOwGBlCjeCH0BRJQbQ/opHL17w==} - - parse-ms@2.1.0: - resolution: {integrity: sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==} - engines: {node: '>=6'} - - parseurl@1.3.3: - resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} - engines: {node: '>= 0.8'} - - path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} - path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -3144,9 +2151,6 @@ packages: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} - path-to-regexp@0.1.12: - resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} - pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} @@ -3156,9 +2160,6 @@ packages: performance-now@2.1.0: resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} - periscopic@3.1.0: - resolution: {integrity: sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==} - picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -3166,11 +2167,6 @@ packages: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} - pidtree@0.6.0: - resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} - engines: {node: '>=0.10'} - hasBin: true - pify@2.3.0: resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} engines: {node: '>=0.10.0'} @@ -3179,19 +2175,10 @@ packages: resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} engines: {node: '>= 6'} - pkg-types@1.2.1: - resolution: {integrity: sha512-sQoqa8alT3nHjGuTjuKgOnvjo4cljkufdtLMnO2LBP/wRwuDlo1tkaEdMxCRhyGRPacv/ztlZgDPm2b7FAmEvw==} - possible-typed-array-names@1.0.0: resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} engines: {node: '>= 0.4'} - postcss-discard-duplicates@5.1.0: - resolution: {integrity: sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - postcss-import@15.1.0: resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} engines: {node: '>=14.0.0'} @@ -3216,35 +2203,6 @@ packages: ts-node: optional: true - postcss-modules-extract-imports@3.1.0: - resolution: {integrity: sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==} - engines: {node: ^10 || ^12 || >= 14} - peerDependencies: - postcss: ^8.1.0 - - postcss-modules-local-by-default@4.1.0: - resolution: {integrity: sha512-rm0bdSv4jC3BDma3s9H19ZddW0aHX6EoqwDYU2IfZhRN+53QrufTRo2IdkAbRqLx4R2IYbZnbjKKxg4VN5oU9Q==} - engines: {node: ^10 || ^12 || >= 14} - peerDependencies: - postcss: ^8.1.0 - - postcss-modules-scope@3.2.1: - resolution: {integrity: sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==} - engines: {node: ^10 || ^12 || >= 14} - peerDependencies: - postcss: ^8.1.0 - - postcss-modules-values@4.0.0: - resolution: {integrity: sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==} - engines: {node: ^10 || ^12 || >= 14} - peerDependencies: - postcss: ^8.1.0 - - postcss-modules@6.0.1: - resolution: {integrity: sha512-zyo2sAkVvuZFFy0gc2+4O+xar5dYlaVy/ebO24KT0ftk/iJevSNyPyQellsBLlnccwh7f6V6Y4GvuKRYToNgpQ==} - peerDependencies: - postcss: ^8.0.0 - postcss-nested@6.2.0: resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} engines: {node: '>=12.0'} @@ -3255,10 +2213,6 @@ packages: resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} engines: {node: '>=4'} - postcss-selector-parser@7.0.0: - resolution: {integrity: sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==} - engines: {node: '>=4'} - postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} @@ -3271,10 +2225,6 @@ packages: engines: {node: '>=10.13.0'} hasBin: true - pretty-ms@7.0.1: - resolution: {integrity: sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==} - engines: {node: '>=10'} - proc-log@3.0.0: resolution: {integrity: sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -3294,22 +2244,12 @@ packages: resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} engines: {node: '>=10'} - property-information@6.5.0: - resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==} - - proxy-addr@2.0.7: - resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} - engines: {node: '>= 0.10'} - psl@1.15.0: resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==} pump@2.0.1: resolution: {integrity: sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==} - pump@3.0.2: - resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==} - pumpify@1.5.1: resolution: {integrity: sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==} @@ -3317,10 +2257,6 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - qs@6.13.0: - resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} - engines: {node: '>=0.6'} - qs@6.5.3: resolution: {integrity: sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==} engines: {node: '>=0.6'} @@ -3328,14 +2264,6 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - range-parser@1.2.1: - resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} - engines: {node: '>= 0.6'} - - raw-body@2.5.2: - resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} - engines: {node: '>= 0.8'} - react-aria-components@1.5.0: resolution: {integrity: sha512-wzf0g6cvWrqAJd4FkisAfFnslx6AJREgOd/NEmVE/RGuDxGTzss4awcwbo98rIVmqbTTFApiygy0SyWGrRZfDA==} peerDependencies: @@ -3386,6 +2314,16 @@ packages: peerDependencies: react: '>=16.8' + react-router@7.1.1: + resolution: {integrity: sha512-39sXJkftkKWRZ2oJtHhCxmoCrBCULr/HAH4IT5DHlgu/Q0FCPV0S4Lx+abjDTx/74xoZzNYDYbOZWlJjruyuDQ==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + peerDependenciesMeta: + react-dom: + optional: true + react-stately@3.34.0: resolution: {integrity: sha512-0N9tZ8qQ/CxpJH7ao0O6gr+8955e7VrOskg9N+TIxkFknPetwOCtgppMYhnTfteBV8WfM/vv4OC1NbkgYTqXJA==} peerDependencies: @@ -3401,33 +2339,17 @@ packages: readable-stream@2.3.8: resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} - readable-stream@3.6.2: - resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} - engines: {node: '>= 6'} - readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} + readdirp@4.0.2: + resolution: {integrity: sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==} + engines: {node: '>= 14.16.0'} + regenerator-runtime@0.14.1: resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} - remark-frontmatter@4.0.1: - resolution: {integrity: sha512-38fJrB0KnmD3E33a5jZC/5+gGAC2WKNiPw1/fdXJvijBlhA7RCsvJklrYJakS0HedninvaCYW8lQGf9C918GfA==} - - remark-mdx-frontmatter@1.1.1: - resolution: {integrity: sha512-7teX9DW4tI2WZkXS4DBxneYSY7NHiXl4AKdWDO9LXVweULlCT8OPWsOjLEnMIXViN1j+QcY8mfbq3k0EK6x3uA==} - engines: {node: '>=12.2.0'} - - remark-mdx@2.3.0: - resolution: {integrity: sha512-g53hMkpM0I98MU266IzDFMrTD980gNF3BJnkyFcmN+dD873mQeD5rdMO3Y2X+x8umQfbSE0PcoEDl7ledSA+2g==} - - remark-parse@10.0.2: - resolution: {integrity: sha512-3ydxgHa/ZQzG8LvC7jTXccARYDcRld3VfcgIIFs7bI6vbRSxJJmzgLEIIoYKyrfhaY+ujuWaf/PJiMZXoiCXgw==} - - remark-rehype@10.1.0: - resolution: {integrity: sha512-EFmR5zppdBp0WQeDVZ/b66CWJipB2q2VLNFMabzDSGR66Z2fQii83G5gTBbgGEnEEA0QRussvrFHxk1HWGJskw==} - remix-utils@7.7.0: resolution: {integrity: sha512-J8NhP044nrNIam/xOT1L9a4RQ9FSaA2wyrUwmN8ZT+c/+CdAAf70yfaLnvMyKcV5U+8BcURQ/aVbth77sT6jGA==} engines: {node: '>=18.0.0'} @@ -3466,21 +2388,10 @@ packages: engines: {node: '>= 6'} deprecated: request has been deprecated, see https://github.com/request/request/issues/3142 - require-like@0.1.2: - resolution: {integrity: sha512-oyrU88skkMtDdauHDuKVrgR+zuItqr6/c//FXzvmxRGMexSDc6hNvJInGW3LL46n+8b50RykrvwSUIIQH2LQ5A==} - - resolve.exports@2.0.3: - resolution: {integrity: sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==} - engines: {node: '>=10'} - resolve@1.22.8: resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} hasBin: true - restore-cursor@3.1.0: - resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} - engines: {node: '>=8'} - retry@0.12.0: resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} engines: {node: '>= 4'} @@ -3496,11 +2407,6 @@ packages: resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==} hasBin: true - rollup@4.24.4: - resolution: {integrity: sha512-vGorVWIsWfX3xbcyAS+I047kFKapHYivmkaT63Smj77XwvLSJos6M1xGqZnBPFQFBRZDOcG1QnYEIxAvTr/HjA==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true - rollup@4.28.1: resolution: {integrity: sha512-61fXYl/qNVinKmGSTHAZ6Yy8I3YIJC/r2m9feHo6SwVAVcLT5MPwOUFe7EuURA/4m0NR8lXG4BBXuo/IZEsjMg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -3509,10 +2415,6 @@ packages: run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - sade@1.8.1: - resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} - engines: {node: '>=6'} - safe-buffer@5.1.2: resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} @@ -3534,14 +2436,6 @@ packages: engines: {node: '>=10'} hasBin: true - send@0.19.0: - resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} - engines: {node: '>= 0.8.0'} - - serve-static@1.16.2: - resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} - engines: {node: '>= 0.8.0'} - set-cookie-parser@2.7.1: resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} @@ -3549,9 +2443,6 @@ packages: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} - setprototypeof@1.2.0: - resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -3560,13 +2451,6 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - side-channel@1.0.6: - resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} - engines: {node: '>= 0.4'} - - signal-exit@3.0.7: - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} @@ -3586,9 +2470,6 @@ packages: resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} engines: {node: '>= 8'} - space-separated-tokens@2.0.2: - resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} - spdx-correct@3.2.0: resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} @@ -3606,14 +2487,6 @@ packages: engines: {node: '>=0.10.0'} hasBin: true - ssri@10.0.6: - resolution: {integrity: sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - statuses@2.0.1: - resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} - engines: {node: '>= 0.8'} - stream-buffers@3.0.3: resolution: {integrity: sha512-pqMqwQCso0PBJt2PQmDO0cFj0lyqmiwOMiMSkVtRokl7e+ZTRYgDHKnuZNbqjiJXgsg4nuqtD/zxuo9KqTp0Yw==} engines: {node: '>= 0.10.0'} @@ -3624,9 +2497,6 @@ packages: stream-slice@0.1.2: resolution: {integrity: sha512-QzQxpoacatkreL6jsxnVb7X5R/pGw9OUv2qWTYWnmLpg4NdN31snPy/f3TdQE1ZUXaThRvj1Zw4/OGg0ZkaLMA==} - string-hash@1.1.3: - resolution: {integrity: sha512-kJUvRUFK49aub+a7T1nNE66EJbZBMnBgoC1UbCZ5n6bsZKBRga4KgBRTMn/pFkeCZSYtNeSyMxPDM0AXWELk2A==} - string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -3638,12 +2508,6 @@ packages: string_decoder@1.1.1: resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} - string_decoder@1.3.0: - resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} - - stringify-entities@4.0.4: - resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} - strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -3652,29 +2516,14 @@ packages: resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} engines: {node: '>=12'} - strip-bom@3.0.0: - resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} - engines: {node: '>=4'} - - strip-final-newline@2.0.0: - resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} - engines: {node: '>=6'} - style-mod@4.1.2: resolution: {integrity: sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==} - style-to-object@0.4.4: - resolution: {integrity: sha512-HYNoHZa2GorYNyqiCaBgsxvcJIn7OHq6inEga+E6Ke3m5JkoqpQbnFssk4jwe+K7AhGa2fcha4wSOf1Kn01dMg==} - sucrase@3.35.0: resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} engines: {node: '>=16 || 14 >=14.17'} hasBin: true - supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} - supports-preserve-symlinks-flag@1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} @@ -3697,17 +2546,6 @@ packages: engines: {node: '>=14.0.0'} hasBin: true - tar-fs@2.1.1: - resolution: {integrity: sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==} - - tar-stream@2.2.0: - resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} - engines: {node: '>=6'} - - tar@6.2.1: - resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} - engines: {node: '>=10'} - tar@7.4.3: resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==} engines: {node: '>=18'} @@ -3726,23 +2564,10 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} - toidentifier@1.0.1: - resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} - engines: {node: '>=0.6'} - - toml@3.0.0: - resolution: {integrity: sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==} - tough-cookie@2.5.0: resolution: {integrity: sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==} engines: {node: '>=0.8'} - trim-lines@3.0.1: - resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} - - trough@2.2.0: - resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} - ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} @@ -3756,10 +2581,6 @@ packages: typescript: optional: true - tsconfig-paths@4.2.0: - resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} - engines: {node: '>=6'} - tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} @@ -3779,18 +2600,11 @@ packages: resolution: {integrity: sha512-yOGpmOAL7CkKe/91I5O3gPICmJNLJ1G4zFYVAsRHg7M64biSnPtRj0WNQt++bRkjYOqjWXrhnUw1utzmVErAdg==} engines: {node: '>=16'} - type-is@1.6.18: - resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} - engines: {node: '>= 0.6'} - typescript@5.7.2: resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==} engines: {node: '>=14.17'} hasBin: true - ufo@1.5.4: - resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==} - undici-types@6.20.0: resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} @@ -3802,49 +2616,10 @@ packages: resolution: {integrity: sha512-3+mdX2R31khuLCm2mKExSlMdJsfol7bJkIMH80tdXA74W34rT1jKemUTlYR7WY3TqsV4wfOgpatWmmB2Jl1+5g==} engines: {node: '>=20.18.1'} - unified@10.1.2: - resolution: {integrity: sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==} - - unique-filename@3.0.0: - resolution: {integrity: sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - unique-slug@4.0.0: - resolution: {integrity: sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - unist-util-generated@2.0.1: - resolution: {integrity: sha512-qF72kLmPxAw0oN2fwpWIqbXAVyEqUzDHMsbtPvOudIlUzXYFIeQIuxXQCRCFh22B7cixvU0MG7m3MW8FTq/S+A==} - - unist-util-is@5.2.1: - resolution: {integrity: sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==} - - unist-util-position-from-estree@1.1.2: - resolution: {integrity: sha512-poZa0eXpS+/XpoQwGwl79UUdea4ol2ZuCYguVaJS4qzIOMDzbqz8a3erUCOmubSZkaOuGamb3tX790iwOIROww==} - - unist-util-position@4.0.4: - resolution: {integrity: sha512-kUBE91efOWfIVBo8xzh/uZQ7p9ffYRtUbMRZBNFYwf0RK8koUMx6dGUfwylLOKmaT2cs4wSW96QoYUSXAyEtpg==} - - unist-util-remove-position@4.0.2: - resolution: {integrity: sha512-TkBb0HABNmxzAcfLf4qsIbFbaPDvMO6wa3b3j4VcEzFVaw1LBKwnW4/sRJ/atSLSzoIg41JWEdnE7N6DIhGDGQ==} - - unist-util-stringify-position@3.0.3: - resolution: {integrity: sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==} - - unist-util-visit-parents@5.1.3: - resolution: {integrity: sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==} - - unist-util-visit@4.1.2: - resolution: {integrity: sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==} - universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} - unpipe@1.0.0: - resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} - engines: {node: '>= 0.8'} - update-browserslist-db@1.1.1: resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==} hasBin: true @@ -3871,20 +2646,11 @@ packages: util@0.12.5: resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==} - utils-merge@1.0.1: - resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} - engines: {node: '>= 0.4.0'} - uuid@3.4.0: resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==} deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details. hasBin: true - uvu@0.5.6: - resolution: {integrity: sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==} - engines: {node: '>=8'} - hasBin: true - valibot@0.41.0: resolution: {integrity: sha512-igDBb8CTYr8YTQlOKgaN9nSS0Be7z+WRuaeYqGf3Cjz3aKmSnqEmYnkfVjzIuumGqfHpa3fLIvMEAfhrpqN8ng==} peerDependencies: @@ -3900,23 +2666,13 @@ packages: resolution: {integrity: sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - vary@1.1.2: - resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} - engines: {node: '>= 0.8'} - verror@1.10.0: resolution: {integrity: sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==} engines: {'0': node >=0.6.0} - vfile-message@3.1.4: - resolution: {integrity: sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==} - - vfile@5.3.7: - resolution: {integrity: sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==} - - vite-node@1.6.0: - resolution: {integrity: sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw==} - engines: {node: ^18.0.0 || >=20.0.0} + vite-node@3.0.0-beta.2: + resolution: {integrity: sha512-ofTf6cfRdL30Wbl9n/BX81EyIR5s4PReLmSurrxQ+koLaWUNOEo8E0lCM53OJkb8vpa2URM2nSrxZsIFyvY1rg==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true vite-plugin-babel@1.3.0: @@ -3933,37 +2689,6 @@ packages: vite: optional: true - vite@5.4.11: - resolution: {integrity: sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==} - engines: {node: ^18.0.0 || >=20.0.0} - hasBin: true - peerDependencies: - '@types/node': ^18.0.0 || >=20.0.0 - less: '*' - lightningcss: ^1.21.0 - sass: '*' - sass-embedded: '*' - stylus: '*' - sugarss: '*' - terser: ^5.4.0 - peerDependenciesMeta: - '@types/node': - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - vite@6.0.3: resolution: {integrity: sha512-Cmuo5P0ENTN6HxLSo6IHsjCLn/81Vgrp81oaiFFMRa8gGDj5xEjIcEpf2ZymZtZR8oU0P2JX5WuUp/rlXcHkAw==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -4007,9 +2732,6 @@ packages: w3c-keyname@2.2.8: resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} - wcwidth@1.0.1: - resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} - web-encoding@1.1.5: resolution: {integrity: sha512-HYLeVCdJ0+lBYV2FvNZmv3HJ2Nt0QYXqZojk3d9FJOLkwnuhzM9tmamh8d7HPM8QqjKH8DeHkFTx+CFlWpZZDA==} @@ -4042,18 +2764,6 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - ws@7.5.10: - resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==} - engines: {node: '>=8.3.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ^5.0.2 - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - ws@8.18.0: resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} engines: {node: '>=10.0.0'} @@ -4073,9 +2783,6 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - yallist@4.0.0: - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - yallist@5.0.0: resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} engines: {node: '>=18'} @@ -4085,16 +2792,9 @@ packages: engines: {node: '>= 14'} hasBin: true - yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} - zod@3.23.8: resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} - zwitch@2.0.4: - resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} - snapshots: '@alloc/quick-lru@5.2.0': {} @@ -4301,6 +3001,41 @@ snapshots: '@babel/helper-string-parser': 7.25.9 '@babel/helper-validator-identifier': 7.25.9 + '@biomejs/biome@1.9.4': + optionalDependencies: + '@biomejs/cli-darwin-arm64': 1.9.4 + '@biomejs/cli-darwin-x64': 1.9.4 + '@biomejs/cli-linux-arm64': 1.9.4 + '@biomejs/cli-linux-arm64-musl': 1.9.4 + '@biomejs/cli-linux-x64': 1.9.4 + '@biomejs/cli-linux-x64-musl': 1.9.4 + '@biomejs/cli-win32-arm64': 1.9.4 + '@biomejs/cli-win32-x64': 1.9.4 + + '@biomejs/cli-darwin-arm64@1.9.4': + optional: true + + '@biomejs/cli-darwin-x64@1.9.4': + optional: true + + '@biomejs/cli-linux-arm64-musl@1.9.4': + optional: true + + '@biomejs/cli-linux-arm64@1.9.4': + optional: true + + '@biomejs/cli-linux-x64-musl@1.9.4': + optional: true + + '@biomejs/cli-linux-x64@1.9.4': + optional: true + + '@biomejs/cli-win32-arm64@1.9.4': + optional: true + + '@biomejs/cli-win32-x64@1.9.4': + optional: true + '@codemirror/autocomplete@6.18.2(@codemirror/language@6.10.3)(@codemirror/state@6.4.1)(@codemirror/view@6.34.1)(@lezer/common@1.2.3)': dependencies: '@codemirror/language': 6.10.3 @@ -4391,212 +3126,75 @@ snapshots: react: 19.0.0 tslib: 2.6.2 - '@emotion/hash@0.9.2': {} - - '@esbuild/aix-ppc64@0.21.5': - optional: true - '@esbuild/aix-ppc64@0.24.0': optional: true - '@esbuild/android-arm64@0.17.6': - optional: true - - '@esbuild/android-arm64@0.21.5': - optional: true - '@esbuild/android-arm64@0.24.0': optional: true - '@esbuild/android-arm@0.17.6': - optional: true - - '@esbuild/android-arm@0.21.5': - optional: true - '@esbuild/android-arm@0.24.0': optional: true - '@esbuild/android-x64@0.17.6': - optional: true - - '@esbuild/android-x64@0.21.5': - optional: true - '@esbuild/android-x64@0.24.0': optional: true - '@esbuild/darwin-arm64@0.17.6': - optional: true - - '@esbuild/darwin-arm64@0.21.5': - optional: true - '@esbuild/darwin-arm64@0.24.0': optional: true - '@esbuild/darwin-x64@0.17.6': - optional: true - - '@esbuild/darwin-x64@0.21.5': - optional: true - '@esbuild/darwin-x64@0.24.0': optional: true - '@esbuild/freebsd-arm64@0.17.6': - optional: true - - '@esbuild/freebsd-arm64@0.21.5': - optional: true - '@esbuild/freebsd-arm64@0.24.0': optional: true - '@esbuild/freebsd-x64@0.17.6': - optional: true - - '@esbuild/freebsd-x64@0.21.5': - optional: true - '@esbuild/freebsd-x64@0.24.0': optional: true - '@esbuild/linux-arm64@0.17.6': - optional: true - - '@esbuild/linux-arm64@0.21.5': - optional: true - '@esbuild/linux-arm64@0.24.0': optional: true - '@esbuild/linux-arm@0.17.6': - optional: true - - '@esbuild/linux-arm@0.21.5': - optional: true - '@esbuild/linux-arm@0.24.0': optional: true - '@esbuild/linux-ia32@0.17.6': - optional: true - - '@esbuild/linux-ia32@0.21.5': - optional: true - '@esbuild/linux-ia32@0.24.0': optional: true - '@esbuild/linux-loong64@0.17.6': - optional: true - - '@esbuild/linux-loong64@0.21.5': - optional: true - '@esbuild/linux-loong64@0.24.0': optional: true - '@esbuild/linux-mips64el@0.17.6': - optional: true - - '@esbuild/linux-mips64el@0.21.5': - optional: true - '@esbuild/linux-mips64el@0.24.0': optional: true - '@esbuild/linux-ppc64@0.17.6': - optional: true - - '@esbuild/linux-ppc64@0.21.5': - optional: true - '@esbuild/linux-ppc64@0.24.0': optional: true - '@esbuild/linux-riscv64@0.17.6': - optional: true - - '@esbuild/linux-riscv64@0.21.5': - optional: true - '@esbuild/linux-riscv64@0.24.0': optional: true - '@esbuild/linux-s390x@0.17.6': - optional: true - - '@esbuild/linux-s390x@0.21.5': - optional: true - '@esbuild/linux-s390x@0.24.0': optional: true - '@esbuild/linux-x64@0.17.6': - optional: true - - '@esbuild/linux-x64@0.21.5': - optional: true - '@esbuild/linux-x64@0.24.0': optional: true - '@esbuild/netbsd-x64@0.17.6': - optional: true - - '@esbuild/netbsd-x64@0.21.5': - optional: true - '@esbuild/netbsd-x64@0.24.0': optional: true '@esbuild/openbsd-arm64@0.24.0': optional: true - '@esbuild/openbsd-x64@0.17.6': - optional: true - - '@esbuild/openbsd-x64@0.21.5': - optional: true - '@esbuild/openbsd-x64@0.24.0': optional: true - '@esbuild/sunos-x64@0.17.6': - optional: true - - '@esbuild/sunos-x64@0.21.5': - optional: true - '@esbuild/sunos-x64@0.24.0': optional: true - '@esbuild/win32-arm64@0.17.6': - optional: true - - '@esbuild/win32-arm64@0.21.5': - optional: true - '@esbuild/win32-arm64@0.24.0': optional: true - '@esbuild/win32-ia32@0.17.6': - optional: true - - '@esbuild/win32-ia32@0.21.5': - optional: true - '@esbuild/win32-ia32@0.24.0': optional: true - '@esbuild/win32-x64@0.17.6': - optional: true - - '@esbuild/win32-x64@0.21.5': - optional: true - '@esbuild/win32-x64@0.24.0': optional: true @@ -4680,8 +3278,6 @@ snapshots: dependencies: jsep: 1.4.0 - '@jspm/core@2.0.1': {} - '@kubernetes/client-node@0.22.3': dependencies: byline: 5.0.0 @@ -4710,27 +3306,7 @@ snapshots: dependencies: '@lezer/common': 1.2.3 - '@mdx-js/mdx@2.3.0': - dependencies: - '@types/estree-jsx': 1.0.5 - '@types/mdx': 2.0.13 - estree-util-build-jsx: 2.2.2 - estree-util-is-identifier-name: 2.1.0 - estree-util-to-js: 1.2.0 - estree-walker: 3.0.3 - hast-util-to-estree: 2.3.3 - markdown-extensions: 1.1.1 - periscopic: 3.1.0 - remark-mdx: 2.3.0 - remark-parse: 10.0.2 - remark-rehype: 10.1.0 - unified: 10.1.2 - unist-util-position-from-estree: 1.1.2 - unist-util-stringify-position: 3.0.3 - unist-util-visit: 4.1.2 - vfile: 5.3.7 - transitivePeerDependencies: - - supports-color + '@mjackson/node-fetch-server@0.2.0': {} '@nodelib/fs.scandir@2.1.5': dependencies: @@ -4744,10 +3320,6 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.17.1 - '@npmcli/fs@3.1.1': - dependencies: - semver: 7.6.3 - '@npmcli/git@4.1.0': dependencies: '@npmcli/promise-spawn': 6.0.2 @@ -5374,6 +3946,67 @@ snapshots: '@swc/helpers': 0.5.15 react: 19.0.0 + '@react-router/dev@7.1.1(@types/node@22.10.1)(jiti@1.21.6)(react-router@7.1.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(typescript@5.7.2)(vite@6.0.3(@types/node@22.10.1)(jiti@1.21.6)(yaml@2.6.1))(yaml@2.6.1)': + dependencies: + '@babel/core': 7.26.0 + '@babel/generator': 7.26.3 + '@babel/parser': 7.26.3 + '@babel/plugin-syntax-decorators': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.0) + '@babel/preset-typescript': 7.26.0(@babel/core@7.26.0) + '@babel/traverse': 7.26.4 + '@babel/types': 7.26.3 + '@npmcli/package-json': 4.0.1 + '@react-router/node': 7.1.1(react-router@7.1.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(typescript@5.7.2) + arg: 5.0.2 + babel-dead-code-elimination: 1.0.8 + chokidar: 4.0.3 + dedent: 1.5.3 + es-module-lexer: 1.5.4 + exit-hook: 2.2.1 + fs-extra: 10.1.0 + gunzip-maybe: 1.4.2 + jsesc: 3.0.2 + lodash: 4.17.21 + pathe: 1.1.2 + picocolors: 1.1.1 + picomatch: 2.3.1 + prettier: 2.8.8 + react-refresh: 0.14.2 + react-router: 7.1.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + semver: 7.6.3 + set-cookie-parser: 2.7.1 + valibot: 0.41.0(typescript@5.7.2) + vite: 6.0.3(@types/node@22.10.1)(jiti@1.21.6)(yaml@2.6.1) + vite-node: 3.0.0-beta.2(@types/node@22.10.1)(jiti@1.21.6)(yaml@2.6.1) + optionalDependencies: + typescript: 5.7.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - bluebird + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + '@react-router/node@7.1.1(react-router@7.1.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(typescript@5.7.2)': + dependencies: + '@mjackson/node-fetch-server': 0.2.0 + react-router: 7.1.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + source-map-support: 0.5.21 + stream-slice: 0.1.2 + undici: 6.21.0 + optionalDependencies: + typescript: 5.7.2 + '@react-stately/calendar@3.6.0(react@19.0.0)': dependencies: '@internationalized/date': 3.6.0 @@ -5770,83 +4403,6 @@ snapshots: '@react-types/shared': 3.26.0(react@19.0.0) react: 19.0.0 - '@remix-run/dev@2.15.0(@remix-run/react@2.15.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2))(@types/node@22.10.1)(typescript@5.7.2)(vite@6.0.3(@types/node@22.10.1)(jiti@1.21.6)(yaml@2.6.1))': - dependencies: - '@babel/core': 7.26.0 - '@babel/generator': 7.26.3 - '@babel/parser': 7.26.3 - '@babel/plugin-syntax-decorators': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.0) - '@babel/preset-typescript': 7.26.0(@babel/core@7.26.0) - '@babel/traverse': 7.26.4 - '@babel/types': 7.26.3 - '@mdx-js/mdx': 2.3.0 - '@npmcli/package-json': 4.0.1 - '@remix-run/node': 2.15.0(typescript@5.7.2) - '@remix-run/react': 2.15.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2) - '@remix-run/router': 1.21.0 - '@remix-run/server-runtime': 2.15.0(typescript@5.7.2) - '@types/mdx': 2.0.13 - '@vanilla-extract/integration': 6.5.0(@types/node@22.10.1) - arg: 5.0.2 - cacache: 17.1.4 - chalk: 4.1.2 - chokidar: 3.6.0 - cross-spawn: 7.0.6 - dotenv: 16.4.7 - es-module-lexer: 1.5.4 - esbuild: 0.17.6 - esbuild-plugins-node-modules-polyfill: 1.6.8(esbuild@0.17.6) - execa: 5.1.1 - exit-hook: 2.2.1 - express: 4.21.2 - fs-extra: 10.1.0 - get-port: 5.1.1 - gunzip-maybe: 1.4.2 - jsesc: 3.0.2 - json5: 2.2.3 - lodash: 4.17.21 - lodash.debounce: 4.0.8 - minimatch: 9.0.5 - ora: 5.4.1 - picocolors: 1.1.1 - picomatch: 2.3.1 - pidtree: 0.6.0 - postcss: 8.4.49 - postcss-discard-duplicates: 5.1.0(postcss@8.4.49) - postcss-load-config: 4.0.2(postcss@8.4.49) - postcss-modules: 6.0.1(postcss@8.4.49) - prettier: 2.8.8 - pretty-ms: 7.0.1 - react-refresh: 0.14.2 - remark-frontmatter: 4.0.1 - remark-mdx-frontmatter: 1.1.1 - semver: 7.6.3 - set-cookie-parser: 2.7.1 - tar-fs: 2.1.1 - tsconfig-paths: 4.2.0 - valibot: 0.41.0(typescript@5.7.2) - vite-node: 1.6.0(@types/node@22.10.1) - ws: 7.5.10 - optionalDependencies: - typescript: 5.7.2 - vite: 6.0.3(@types/node@22.10.1)(jiti@1.21.6)(yaml@2.6.1) - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - bluebird - - bufferutil - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - ts-node - - utf-8-validate - '@remix-run/node@2.15.0(typescript@5.7.2)': dependencies: '@remix-run/server-runtime': 2.15.0(typescript@5.7.2) @@ -5858,6 +4414,7 @@ snapshots: undici: 6.21.0 optionalDependencies: typescript: 5.7.2 + optional: true '@remix-run/react@2.15.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2)': dependencies: @@ -5870,15 +4427,10 @@ snapshots: turbo-stream: 2.4.0 optionalDependencies: typescript: 5.7.2 + optional: true - '@remix-run/route-config@2.15.0(@remix-run/dev@2.15.0(@remix-run/react@2.15.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2))(@types/node@22.10.1)(typescript@5.7.2)(vite@6.0.3(@types/node@22.10.1)(jiti@1.21.6)(yaml@2.6.1)))(typescript@5.7.2)': - dependencies: - '@remix-run/dev': 2.15.0(@remix-run/react@2.15.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2))(@types/node@22.10.1)(typescript@5.7.2)(vite@6.0.3(@types/node@22.10.1)(jiti@1.21.6)(yaml@2.6.1)) - lodash: 4.17.21 - optionalDependencies: - typescript: 5.7.2 - - '@remix-run/router@1.21.0': {} + '@remix-run/router@1.21.0': + optional: true '@remix-run/server-runtime@2.15.0(typescript@5.7.2)': dependencies: @@ -5891,11 +4443,13 @@ snapshots: turbo-stream: 2.4.0 optionalDependencies: typescript: 5.7.2 + optional: true '@remix-run/web-blob@3.1.0': dependencies: '@remix-run/web-stream': 1.1.0 web-encoding: 1.1.5 + optional: true '@remix-run/web-fetch@4.4.2': dependencies: @@ -5907,127 +4461,77 @@ snapshots: abort-controller: 3.0.0 data-uri-to-buffer: 3.0.1 mrmime: 1.0.1 + optional: true '@remix-run/web-file@3.1.0': dependencies: '@remix-run/web-blob': 3.1.0 + optional: true '@remix-run/web-form-data@3.1.0': dependencies: web-encoding: 1.1.5 + optional: true '@remix-run/web-stream@1.1.0': dependencies: web-streams-polyfill: 3.3.3 - - '@rollup/rollup-android-arm-eabi@4.24.4': optional: true '@rollup/rollup-android-arm-eabi@4.28.1': optional: true - '@rollup/rollup-android-arm64@4.24.4': - optional: true - '@rollup/rollup-android-arm64@4.28.1': optional: true - '@rollup/rollup-darwin-arm64@4.24.4': - optional: true - '@rollup/rollup-darwin-arm64@4.28.1': optional: true - '@rollup/rollup-darwin-x64@4.24.4': - optional: true - '@rollup/rollup-darwin-x64@4.28.1': optional: true - '@rollup/rollup-freebsd-arm64@4.24.4': - optional: true - '@rollup/rollup-freebsd-arm64@4.28.1': optional: true - '@rollup/rollup-freebsd-x64@4.24.4': - optional: true - '@rollup/rollup-freebsd-x64@4.28.1': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.24.4': - optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.28.1': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.24.4': - optional: true - '@rollup/rollup-linux-arm-musleabihf@4.28.1': optional: true - '@rollup/rollup-linux-arm64-gnu@4.24.4': - optional: true - '@rollup/rollup-linux-arm64-gnu@4.28.1': optional: true - '@rollup/rollup-linux-arm64-musl@4.24.4': - optional: true - '@rollup/rollup-linux-arm64-musl@4.28.1': optional: true '@rollup/rollup-linux-loongarch64-gnu@4.28.1': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.24.4': - optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.28.1': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.24.4': - optional: true - '@rollup/rollup-linux-riscv64-gnu@4.28.1': optional: true - '@rollup/rollup-linux-s390x-gnu@4.24.4': - optional: true - '@rollup/rollup-linux-s390x-gnu@4.28.1': optional: true - '@rollup/rollup-linux-x64-gnu@4.24.4': - optional: true - '@rollup/rollup-linux-x64-gnu@4.28.1': optional: true - '@rollup/rollup-linux-x64-musl@4.24.4': - optional: true - '@rollup/rollup-linux-x64-musl@4.28.1': optional: true - '@rollup/rollup-win32-arm64-msvc@4.24.4': - optional: true - '@rollup/rollup-win32-arm64-msvc@4.28.1': optional: true - '@rollup/rollup-win32-ia32-msvc@4.24.4': - optional: true - '@rollup/rollup-win32-ia32-msvc@4.28.1': optional: true - '@rollup/rollup-win32-x64-msvc@4.24.4': - optional: true - '@rollup/rollup-win32-x64-msvc@4.28.1': optional: true @@ -6040,37 +4544,14 @@ snapshots: dependencies: tslib: 2.8.1 - '@types/acorn@4.0.6': - dependencies: - '@types/estree': 1.0.6 - '@types/cookie@0.6.0': {} - '@types/debug@4.1.12': - dependencies: - '@types/ms': 0.7.34 - - '@types/estree-jsx@1.0.5': - dependencies: - '@types/estree': 1.0.6 - '@types/estree@1.0.6': {} - '@types/hast@2.3.10': - dependencies: - '@types/unist': 2.0.11 - - '@types/mdast@3.0.15': - dependencies: - '@types/unist': 2.0.11 - - '@types/mdx@2.0.13': {} - - '@types/ms@0.7.34': {} - '@types/node@22.10.1': dependencies: undici-types: 6.20.0 + optional: true '@types/react-dom@19.0.1': dependencies: @@ -6080,8 +4561,6 @@ snapshots: dependencies: csstype: 3.1.3 - '@types/unist@2.0.11': {} - '@uiw/codemirror-extensions-basic-setup@4.23.6(@codemirror/autocomplete@6.18.2(@codemirror/language@6.10.3)(@codemirror/state@6.4.1)(@codemirror/view@6.34.1)(@lezer/common@1.2.3))(@codemirror/commands@6.7.1)(@codemirror/language@6.10.3)(@codemirror/lint@6.8.2)(@codemirror/search@6.5.7)(@codemirror/state@6.4.1)(@codemirror/view@6.34.1)': dependencies: '@codemirror/autocomplete': 6.18.2(@codemirror/language@6.10.3)(@codemirror/state@6.4.1)(@codemirror/view@6.34.1)(@lezer/common@1.2.3) @@ -6123,59 +4602,8 @@ snapshots: - '@codemirror/lint' - '@codemirror/search' - '@vanilla-extract/babel-plugin-debug-ids@1.1.0': - dependencies: - '@babel/core': 7.26.0 - transitivePeerDependencies: - - supports-color - - '@vanilla-extract/css@1.16.1': - dependencies: - '@emotion/hash': 0.9.2 - '@vanilla-extract/private': 1.0.6 - css-what: 6.1.0 - cssesc: 3.0.0 - csstype: 3.1.3 - dedent: 1.5.3 - deep-object-diff: 1.1.9 - deepmerge: 4.3.1 - lru-cache: 10.4.3 - media-query-parser: 2.0.2 - modern-ahocorasick: 1.1.0 - picocolors: 1.1.1 - transitivePeerDependencies: - - babel-plugin-macros - - '@vanilla-extract/integration@6.5.0(@types/node@22.10.1)': - dependencies: - '@babel/core': 7.26.0 - '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.26.0) - '@vanilla-extract/babel-plugin-debug-ids': 1.1.0 - '@vanilla-extract/css': 1.16.1 - esbuild: 0.17.6 - eval: 0.1.8 - find-up: 5.0.0 - javascript-stringify: 2.1.0 - lodash: 4.17.21 - mlly: 1.7.3 - outdent: 0.8.0 - vite: 5.4.11(@types/node@22.10.1) - vite-node: 1.6.0(@types/node@22.10.1) - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - '@vanilla-extract/private@1.0.6': {} - - '@web3-storage/multipart-parser@1.0.0': {} + '@web3-storage/multipart-parser@1.0.0': + optional: true '@zxing/text-encoding@0.9.0': optional: true @@ -6183,22 +4611,7 @@ snapshots: abort-controller@3.0.0: dependencies: event-target-shim: 5.0.1 - - accepts@1.3.8: - dependencies: - mime-types: 2.1.35 - negotiator: 0.6.3 - - acorn-jsx@5.3.2(acorn@8.14.0): - dependencies: - acorn: 8.14.0 - - acorn@8.14.0: {} - - aggregate-error@3.1.0: - dependencies: - clean-stack: 2.2.0 - indent-string: 4.0.0 + optional: true ajv@6.12.6: dependencies: @@ -6228,16 +4641,12 @@ snapshots: argparse@2.0.1: {} - array-flatten@1.1.1: {} - asn1@0.2.6: dependencies: safer-buffer: 2.1.2 assert-plus@1.0.0: {} - astring@1.9.0: {} - asynckit@0.4.0: {} autoprefixer@10.4.20(postcss@8.4.49): @@ -6253,50 +4662,33 @@ snapshots: available-typed-arrays@1.0.7: dependencies: possible-typed-array-names: 1.0.0 + optional: true aws-sign2@0.7.0: {} aws4@1.13.2: {} + babel-dead-code-elimination@1.0.8: + dependencies: + '@babel/core': 7.26.0 + '@babel/parser': 7.26.3 + '@babel/traverse': 7.26.4 + '@babel/types': 7.26.3 + transitivePeerDependencies: + - supports-color + babel-plugin-react-compiler@19.0.0-beta-df7b47d-20241124: dependencies: '@babel/types': 7.26.3 - bail@2.0.2: {} - balanced-match@1.0.2: {} - base64-js@1.5.1: {} - bcrypt-pbkdf@1.0.2: dependencies: tweetnacl: 0.14.5 binary-extensions@2.3.0: {} - bl@4.1.0: - dependencies: - buffer: 5.7.1 - inherits: 2.0.4 - readable-stream: 3.6.2 - - body-parser@1.20.3: - dependencies: - bytes: 3.1.2 - content-type: 1.0.5 - debug: 2.6.9 - depd: 2.0.0 - destroy: 1.2.0 - http-errors: 2.0.0 - iconv-lite: 0.4.24 - on-finished: 2.4.1 - qs: 6.13.0 - raw-body: 2.5.2 - type-is: 1.6.18 - unpipe: 1.0.0 - transitivePeerDependencies: - - supports-color - brace-expansion@2.0.1: dependencies: balanced-match: 1.0.2 @@ -6318,36 +4710,15 @@ snapshots: buffer-from@1.1.2: {} - buffer@5.7.1: - dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 - byline@5.0.0: {} - bytes@3.1.2: {} - cac@6.7.14: {} - cacache@17.1.4: - dependencies: - '@npmcli/fs': 3.1.1 - fs-minipass: 3.0.3 - glob: 10.4.5 - lru-cache: 7.18.3 - minipass: 7.1.2 - minipass-collect: 1.0.2 - minipass-flush: 1.0.5 - minipass-pipeline: 1.2.4 - p-map: 4.0.0 - ssri: 10.0.6 - tar: 6.2.1 - unique-filename: 3.0.0 - call-bind-apply-helpers@1.0.0: dependencies: es-errors: 1.3.0 function-bind: 1.1.2 + optional: true call-bind@1.0.8: dependencies: @@ -6355,6 +4726,7 @@ snapshots: es-define-property: 1.0.0 get-intrinsic: 1.2.4 set-function-length: 1.2.2 + optional: true camelcase-css@2.0.1: {} @@ -6362,21 +4734,6 @@ snapshots: caseless@0.12.0: {} - ccount@2.0.1: {} - - chalk@4.1.2: - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 - - character-entities-html4@2.1.0: {} - - character-entities-legacy@3.0.0: {} - - character-entities@2.0.2: {} - - character-reference-invalid@2.0.1: {} - chokidar@3.6.0: dependencies: anymatch: 3.1.3 @@ -6389,24 +4746,14 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - chownr@1.1.4: {} - - chownr@2.0.0: {} + chokidar@4.0.3: + dependencies: + readdirp: 4.0.2 chownr@3.0.0: {} - clean-stack@2.2.0: {} - - cli-cursor@3.1.0: - dependencies: - restore-cursor: 3.1.0 - - cli-spinners@2.9.2: {} - client-only@0.0.1: {} - clone@1.0.4: {} - clsx@2.1.1: {} codemirror@6.0.1(@lezer/common@1.2.3): @@ -6431,27 +4778,17 @@ snapshots: dependencies: delayed-stream: 1.0.0 - comma-separated-tokens@2.0.3: {} - commander@4.1.1: {} - confbox@0.1.8: {} - - content-disposition@0.5.4: - dependencies: - safe-buffer: 5.2.1 - - content-type@1.0.5: {} - convert-source-map@2.0.0: {} - cookie-signature@1.0.6: {} + cookie-signature@1.2.2: + optional: true - cookie-signature@1.2.2: {} + cookie@0.6.0: + optional: true - cookie@0.6.0: {} - - cookie@0.7.1: {} + cookie@1.0.2: {} core-util-is@1.0.2: {} @@ -6465,14 +4802,6 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - cross-spawn@7.0.6: - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - - css-what@6.1.0: {} - cssesc@3.0.0: {} csstype@3.1.3: {} @@ -6481,48 +4810,26 @@ snapshots: dependencies: assert-plus: 1.0.0 - data-uri-to-buffer@3.0.1: {} - - debug@2.6.9: - dependencies: - ms: 2.0.0 + data-uri-to-buffer@3.0.1: + optional: true debug@4.4.0: dependencies: ms: 2.1.3 - decode-named-character-reference@1.0.2: - dependencies: - character-entities: 2.0.2 - dedent@1.5.3: {} - deep-object-diff@1.1.9: {} - - deepmerge@4.3.1: {} - - defaults@1.0.4: - dependencies: - clone: 1.0.4 - define-data-property@1.1.4: dependencies: es-define-property: 1.0.0 es-errors: 1.3.0 - gopd: 1.0.1 + gopd: 1.2.0 + optional: true delayed-stream@1.0.0: {} - depd@2.0.0: {} - - dequal@2.0.3: {} - - destroy@1.2.0: {} - didyoumean@1.2.2: {} - diff@5.2.0: {} - dlv@1.1.3: {} dotenv@16.4.7: {} @@ -6541,18 +4848,12 @@ snapshots: jsbn: 0.1.1 safer-buffer: 2.1.2 - ee-first@1.1.1: {} - electron-to-chromium@1.5.51: {} emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} - encodeurl@1.0.2: {} - - encodeurl@2.0.0: {} - end-of-stream@1.4.4: dependencies: once: 1.4.0 @@ -6562,69 +4863,13 @@ snapshots: es-define-property@1.0.0: dependencies: get-intrinsic: 1.2.4 + optional: true - es-errors@1.3.0: {} + es-errors@1.3.0: + optional: true es-module-lexer@1.5.4: {} - esbuild-plugins-node-modules-polyfill@1.6.8(esbuild@0.17.6): - dependencies: - '@jspm/core': 2.0.1 - esbuild: 0.17.6 - local-pkg: 0.5.1 - resolve.exports: 2.0.3 - - esbuild@0.17.6: - optionalDependencies: - '@esbuild/android-arm': 0.17.6 - '@esbuild/android-arm64': 0.17.6 - '@esbuild/android-x64': 0.17.6 - '@esbuild/darwin-arm64': 0.17.6 - '@esbuild/darwin-x64': 0.17.6 - '@esbuild/freebsd-arm64': 0.17.6 - '@esbuild/freebsd-x64': 0.17.6 - '@esbuild/linux-arm': 0.17.6 - '@esbuild/linux-arm64': 0.17.6 - '@esbuild/linux-ia32': 0.17.6 - '@esbuild/linux-loong64': 0.17.6 - '@esbuild/linux-mips64el': 0.17.6 - '@esbuild/linux-ppc64': 0.17.6 - '@esbuild/linux-riscv64': 0.17.6 - '@esbuild/linux-s390x': 0.17.6 - '@esbuild/linux-x64': 0.17.6 - '@esbuild/netbsd-x64': 0.17.6 - '@esbuild/openbsd-x64': 0.17.6 - '@esbuild/sunos-x64': 0.17.6 - '@esbuild/win32-arm64': 0.17.6 - '@esbuild/win32-ia32': 0.17.6 - '@esbuild/win32-x64': 0.17.6 - - esbuild@0.21.5: - optionalDependencies: - '@esbuild/aix-ppc64': 0.21.5 - '@esbuild/android-arm': 0.21.5 - '@esbuild/android-arm64': 0.21.5 - '@esbuild/android-x64': 0.21.5 - '@esbuild/darwin-arm64': 0.21.5 - '@esbuild/darwin-x64': 0.21.5 - '@esbuild/freebsd-arm64': 0.21.5 - '@esbuild/freebsd-x64': 0.21.5 - '@esbuild/linux-arm': 0.21.5 - '@esbuild/linux-arm64': 0.21.5 - '@esbuild/linux-ia32': 0.21.5 - '@esbuild/linux-loong64': 0.21.5 - '@esbuild/linux-mips64el': 0.21.5 - '@esbuild/linux-ppc64': 0.21.5 - '@esbuild/linux-riscv64': 0.21.5 - '@esbuild/linux-s390x': 0.21.5 - '@esbuild/linux-x64': 0.21.5 - '@esbuild/netbsd-x64': 0.21.5 - '@esbuild/openbsd-x64': 0.21.5 - '@esbuild/sunos-x64': 0.21.5 - '@esbuild/win32-arm64': 0.21.5 - '@esbuild/win32-ia32': 0.21.5 - '@esbuild/win32-x64': 0.21.5 - esbuild@0.24.0: optionalDependencies: '@esbuild/aix-ppc64': 0.24.0 @@ -6654,100 +4899,11 @@ snapshots: escalade@3.2.0: {} - escape-html@1.0.3: {} - - estree-util-attach-comments@2.1.1: - dependencies: - '@types/estree': 1.0.6 - - estree-util-build-jsx@2.2.2: - dependencies: - '@types/estree-jsx': 1.0.5 - estree-util-is-identifier-name: 2.1.0 - estree-walker: 3.0.3 - - estree-util-is-identifier-name@1.1.0: {} - - estree-util-is-identifier-name@2.1.0: {} - - estree-util-to-js@1.2.0: - dependencies: - '@types/estree-jsx': 1.0.5 - astring: 1.9.0 - source-map: 0.7.4 - - estree-util-value-to-estree@1.3.0: - dependencies: - is-plain-obj: 3.0.0 - - estree-util-visit@1.2.1: - dependencies: - '@types/estree-jsx': 1.0.5 - '@types/unist': 2.0.11 - - estree-walker@3.0.3: - dependencies: - '@types/estree': 1.0.6 - - etag@1.8.1: {} - - eval@0.1.8: - dependencies: - '@types/node': 22.10.1 - require-like: 0.1.2 - - event-target-shim@5.0.1: {} - - execa@5.1.1: - dependencies: - cross-spawn: 7.0.6 - get-stream: 6.0.1 - human-signals: 2.1.0 - is-stream: 2.0.1 - merge-stream: 2.0.0 - npm-run-path: 4.0.1 - onetime: 5.1.2 - signal-exit: 3.0.7 - strip-final-newline: 2.0.0 + event-target-shim@5.0.1: + optional: true exit-hook@2.2.1: {} - express@4.21.2: - dependencies: - accepts: 1.3.8 - array-flatten: 1.1.1 - body-parser: 1.20.3 - content-disposition: 0.5.4 - content-type: 1.0.5 - cookie: 0.7.1 - cookie-signature: 1.0.6 - debug: 2.6.9 - depd: 2.0.0 - encodeurl: 2.0.0 - escape-html: 1.0.3 - etag: 1.8.1 - finalhandler: 1.3.1 - fresh: 0.5.2 - http-errors: 2.0.0 - merge-descriptors: 1.0.3 - methods: 1.1.2 - on-finished: 2.4.1 - parseurl: 1.3.3 - path-to-regexp: 0.1.12 - proxy-addr: 2.0.7 - qs: 6.13.0 - range-parser: 1.2.1 - safe-buffer: 5.2.1 - send: 0.19.0 - serve-static: 1.16.2 - setprototypeof: 1.2.0 - statuses: 2.0.1 - type-is: 1.6.18 - utils-merge: 1.0.1 - vary: 1.1.2 - transitivePeerDependencies: - - supports-color - extend@3.0.2: {} extsprintf@1.3.0: {} @@ -6768,34 +4924,14 @@ snapshots: dependencies: reusify: 1.0.4 - fault@2.0.1: - dependencies: - format: 0.2.2 - fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 - finalhandler@1.3.1: - dependencies: - debug: 2.6.9 - encodeurl: 2.0.0 - escape-html: 1.0.3 - on-finished: 2.4.1 - parseurl: 1.3.3 - statuses: 2.0.1 - unpipe: 1.0.0 - transitivePeerDependencies: - - supports-color - - find-up@5.0.0: - dependencies: - locate-path: 6.0.0 - path-exists: 4.0.0 - for-each@0.3.3: dependencies: is-callable: 1.2.7 + optional: true foreground-child@3.3.0: dependencies: @@ -6810,39 +4946,19 @@ snapshots: combined-stream: 1.0.8 mime-types: 2.1.35 - format@0.2.2: {} - - forwarded@0.2.0: {} - fraction.js@4.3.7: {} - fresh@0.5.2: {} - - fs-constants@1.0.0: {} - fs-extra@10.1.0: dependencies: graceful-fs: 4.2.11 jsonfile: 6.1.0 universalify: 2.0.1 - fs-minipass@2.1.0: - dependencies: - minipass: 3.3.6 - - fs-minipass@3.0.3: - dependencies: - minipass: 7.1.2 - fsevents@2.3.3: optional: true function-bind@1.1.2: {} - generic-names@4.0.0: - dependencies: - loader-utils: 3.3.1 - gensync@1.0.0-beta.2: {} get-intrinsic@1.2.4: @@ -6850,12 +4966,9 @@ snapshots: es-errors: 1.3.0 function-bind: 1.1.2 has-proto: 1.0.3 - has-symbols: 1.0.3 + has-symbols: 1.1.0 hasown: 2.0.2 - - get-port@5.1.1: {} - - get-stream@6.0.1: {} + optional: true getpass@0.1.7: dependencies: @@ -6882,11 +4995,8 @@ snapshots: globrex@0.1.2: {} - gopd@1.0.1: - dependencies: - get-intrinsic: 1.2.4 - - gopd@1.2.0: {} + gopd@1.2.0: + optional: true graceful-fs@4.2.11: {} @@ -6906,86 +5016,38 @@ snapshots: ajv: 6.12.6 har-schema: 2.0.0 - has-flag@4.0.0: {} - has-property-descriptors@1.0.2: dependencies: es-define-property: 1.0.0 + optional: true - has-proto@1.0.3: {} + has-proto@1.0.3: + optional: true - has-symbols@1.0.3: {} - - has-symbols@1.1.0: {} + has-symbols@1.1.0: + optional: true has-tostringtag@1.0.2: dependencies: has-symbols: 1.1.0 + optional: true hasown@2.0.2: dependencies: function-bind: 1.1.2 - hast-util-to-estree@2.3.3: - dependencies: - '@types/estree': 1.0.6 - '@types/estree-jsx': 1.0.5 - '@types/hast': 2.3.10 - '@types/unist': 2.0.11 - comma-separated-tokens: 2.0.3 - estree-util-attach-comments: 2.1.1 - estree-util-is-identifier-name: 2.1.0 - hast-util-whitespace: 2.0.1 - mdast-util-mdx-expression: 1.3.2 - mdast-util-mdxjs-esm: 1.3.1 - property-information: 6.5.0 - space-separated-tokens: 2.0.2 - style-to-object: 0.4.4 - unist-util-position: 4.0.4 - zwitch: 2.0.4 - transitivePeerDependencies: - - supports-color - - hast-util-whitespace@2.0.1: {} - hosted-git-info@6.1.3: dependencies: lru-cache: 7.18.3 - http-errors@2.0.0: - dependencies: - depd: 2.0.0 - inherits: 2.0.4 - setprototypeof: 1.2.0 - statuses: 2.0.1 - toidentifier: 1.0.1 - http-signature@1.2.0: dependencies: assert-plus: 1.0.0 jsprim: 1.4.2 sshpk: 1.18.0 - human-signals@2.1.0: {} - - iconv-lite@0.4.24: - dependencies: - safer-buffer: 2.1.2 - - icss-utils@5.1.0(postcss@8.4.49): - dependencies: - postcss: 8.4.49 - - ieee754@1.2.1: {} - - imurmurhash@0.1.4: {} - - indent-string@4.0.0: {} - inherits@2.0.4: {} - inline-style-parser@0.1.1: {} - intl-messageformat@10.7.7: dependencies: '@formatjs/ecma402-abstract': 2.2.4 @@ -6993,34 +5055,23 @@ snapshots: '@formatjs/icu-messageformat-parser': 2.9.4 tslib: 2.8.1 - ipaddr.js@1.9.1: {} - - is-alphabetical@2.0.1: {} - - is-alphanumerical@2.0.1: - dependencies: - is-alphabetical: 2.0.1 - is-decimal: 2.0.1 - is-arguments@1.1.1: dependencies: call-bind: 1.0.8 has-tostringtag: 1.0.2 + optional: true is-binary-path@2.1.0: dependencies: binary-extensions: 2.3.0 - is-buffer@2.0.5: {} - - is-callable@1.2.7: {} + is-callable@1.2.7: + optional: true is-core-module@2.15.1: dependencies: hasown: 2.0.2 - is-decimal@2.0.1: {} - is-deflate@1.0.0: {} is-extglob@2.1.1: {} @@ -7030,6 +5081,7 @@ snapshots: is-generator-function@1.0.10: dependencies: has-tostringtag: 1.0.2 + optional: true is-glob@4.0.3: dependencies: @@ -7037,30 +5089,15 @@ snapshots: is-gzip@1.0.0: {} - is-hexadecimal@2.0.1: {} - - is-interactive@1.0.0: {} - is-number@7.0.0: {} - is-plain-obj@3.0.0: {} - - is-plain-obj@4.1.0: {} - - is-reference@3.0.3: - dependencies: - '@types/estree': 1.0.6 - - is-stream@2.0.1: {} - is-typed-array@1.1.13: dependencies: which-typed-array: 1.1.16 + optional: true is-typedarray@1.0.0: {} - is-unicode-supported@0.1.0: {} - isarray@1.0.0: {} isbot@5.1.17: {} @@ -7079,8 +5116,6 @@ snapshots: optionalDependencies: '@pkgjs/parseargs': 0.11.0 - javascript-stringify@2.1.0: {} - jiti@1.21.6: {} jose@5.9.6: @@ -7127,384 +5162,26 @@ snapshots: json-schema: 0.4.0 verror: 1.10.0 - kleur@4.1.5: {} - lilconfig@3.1.1: {} lilconfig@3.1.3: {} lines-and-columns@1.2.4: {} - loader-utils@3.3.1: {} - - local-pkg@0.5.1: - dependencies: - mlly: 1.7.3 - pkg-types: 1.2.1 - - locate-path@6.0.0: - dependencies: - p-locate: 5.0.0 - - lodash.camelcase@4.3.0: {} - lodash.debounce@4.0.8: {} lodash@4.17.21: {} - log-symbols@4.1.0: - dependencies: - chalk: 4.1.2 - is-unicode-supported: 0.1.0 - - longest-streak@3.1.0: {} - lru-cache@10.2.2: {} - lru-cache@10.4.3: {} - lru-cache@5.1.1: dependencies: yallist: 3.1.1 lru-cache@7.18.3: {} - markdown-extensions@1.1.1: {} - - mdast-util-definitions@5.1.2: - dependencies: - '@types/mdast': 3.0.15 - '@types/unist': 2.0.11 - unist-util-visit: 4.1.2 - - mdast-util-from-markdown@1.3.1: - dependencies: - '@types/mdast': 3.0.15 - '@types/unist': 2.0.11 - decode-named-character-reference: 1.0.2 - mdast-util-to-string: 3.2.0 - micromark: 3.2.0 - micromark-util-decode-numeric-character-reference: 1.1.0 - micromark-util-decode-string: 1.1.0 - micromark-util-normalize-identifier: 1.1.0 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - unist-util-stringify-position: 3.0.3 - uvu: 0.5.6 - transitivePeerDependencies: - - supports-color - - mdast-util-frontmatter@1.0.1: - dependencies: - '@types/mdast': 3.0.15 - mdast-util-to-markdown: 1.5.0 - micromark-extension-frontmatter: 1.1.1 - - mdast-util-mdx-expression@1.3.2: - dependencies: - '@types/estree-jsx': 1.0.5 - '@types/hast': 2.3.10 - '@types/mdast': 3.0.15 - mdast-util-from-markdown: 1.3.1 - mdast-util-to-markdown: 1.5.0 - transitivePeerDependencies: - - supports-color - - mdast-util-mdx-jsx@2.1.4: - dependencies: - '@types/estree-jsx': 1.0.5 - '@types/hast': 2.3.10 - '@types/mdast': 3.0.15 - '@types/unist': 2.0.11 - ccount: 2.0.1 - mdast-util-from-markdown: 1.3.1 - mdast-util-to-markdown: 1.5.0 - parse-entities: 4.0.1 - stringify-entities: 4.0.4 - unist-util-remove-position: 4.0.2 - unist-util-stringify-position: 3.0.3 - vfile-message: 3.1.4 - transitivePeerDependencies: - - supports-color - - mdast-util-mdx@2.0.1: - dependencies: - mdast-util-from-markdown: 1.3.1 - mdast-util-mdx-expression: 1.3.2 - mdast-util-mdx-jsx: 2.1.4 - mdast-util-mdxjs-esm: 1.3.1 - mdast-util-to-markdown: 1.5.0 - transitivePeerDependencies: - - supports-color - - mdast-util-mdxjs-esm@1.3.1: - dependencies: - '@types/estree-jsx': 1.0.5 - '@types/hast': 2.3.10 - '@types/mdast': 3.0.15 - mdast-util-from-markdown: 1.3.1 - mdast-util-to-markdown: 1.5.0 - transitivePeerDependencies: - - supports-color - - mdast-util-phrasing@3.0.1: - dependencies: - '@types/mdast': 3.0.15 - unist-util-is: 5.2.1 - - mdast-util-to-hast@12.3.0: - dependencies: - '@types/hast': 2.3.10 - '@types/mdast': 3.0.15 - mdast-util-definitions: 5.1.2 - micromark-util-sanitize-uri: 1.2.0 - trim-lines: 3.0.1 - unist-util-generated: 2.0.1 - unist-util-position: 4.0.4 - unist-util-visit: 4.1.2 - - mdast-util-to-markdown@1.5.0: - dependencies: - '@types/mdast': 3.0.15 - '@types/unist': 2.0.11 - longest-streak: 3.1.0 - mdast-util-phrasing: 3.0.1 - mdast-util-to-string: 3.2.0 - micromark-util-decode-string: 1.1.0 - unist-util-visit: 4.1.2 - zwitch: 2.0.4 - - mdast-util-to-string@3.2.0: - dependencies: - '@types/mdast': 3.0.15 - - media-query-parser@2.0.2: - dependencies: - '@babel/runtime': 7.26.0 - - media-typer@0.3.0: {} - - merge-descriptors@1.0.3: {} - - merge-stream@2.0.0: {} - merge2@1.4.1: {} - methods@1.1.2: {} - - micromark-core-commonmark@1.1.0: - dependencies: - decode-named-character-reference: 1.0.2 - micromark-factory-destination: 1.1.0 - micromark-factory-label: 1.1.0 - micromark-factory-space: 1.1.0 - micromark-factory-title: 1.1.0 - micromark-factory-whitespace: 1.1.0 - micromark-util-character: 1.2.0 - micromark-util-chunked: 1.1.0 - micromark-util-classify-character: 1.1.0 - micromark-util-html-tag-name: 1.2.0 - micromark-util-normalize-identifier: 1.1.0 - micromark-util-resolve-all: 1.1.0 - micromark-util-subtokenize: 1.1.0 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - uvu: 0.5.6 - - micromark-extension-frontmatter@1.1.1: - dependencies: - fault: 2.0.1 - micromark-util-character: 1.2.0 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - - micromark-extension-mdx-expression@1.0.8: - dependencies: - '@types/estree': 1.0.6 - micromark-factory-mdx-expression: 1.0.9 - micromark-factory-space: 1.1.0 - micromark-util-character: 1.2.0 - micromark-util-events-to-acorn: 1.2.3 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - uvu: 0.5.6 - - micromark-extension-mdx-jsx@1.0.5: - dependencies: - '@types/acorn': 4.0.6 - '@types/estree': 1.0.6 - estree-util-is-identifier-name: 2.1.0 - micromark-factory-mdx-expression: 1.0.9 - micromark-factory-space: 1.1.0 - micromark-util-character: 1.2.0 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - uvu: 0.5.6 - vfile-message: 3.1.4 - - micromark-extension-mdx-md@1.0.1: - dependencies: - micromark-util-types: 1.1.0 - - micromark-extension-mdxjs-esm@1.0.5: - dependencies: - '@types/estree': 1.0.6 - micromark-core-commonmark: 1.1.0 - micromark-util-character: 1.2.0 - micromark-util-events-to-acorn: 1.2.3 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - unist-util-position-from-estree: 1.1.2 - uvu: 0.5.6 - vfile-message: 3.1.4 - - micromark-extension-mdxjs@1.0.1: - dependencies: - acorn: 8.14.0 - acorn-jsx: 5.3.2(acorn@8.14.0) - micromark-extension-mdx-expression: 1.0.8 - micromark-extension-mdx-jsx: 1.0.5 - micromark-extension-mdx-md: 1.0.1 - micromark-extension-mdxjs-esm: 1.0.5 - micromark-util-combine-extensions: 1.1.0 - micromark-util-types: 1.1.0 - - micromark-factory-destination@1.1.0: - dependencies: - micromark-util-character: 1.2.0 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - - micromark-factory-label@1.1.0: - dependencies: - micromark-util-character: 1.2.0 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - uvu: 0.5.6 - - micromark-factory-mdx-expression@1.0.9: - dependencies: - '@types/estree': 1.0.6 - micromark-util-character: 1.2.0 - micromark-util-events-to-acorn: 1.2.3 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - unist-util-position-from-estree: 1.1.2 - uvu: 0.5.6 - vfile-message: 3.1.4 - - micromark-factory-space@1.1.0: - dependencies: - micromark-util-character: 1.2.0 - micromark-util-types: 1.1.0 - - micromark-factory-title@1.1.0: - dependencies: - micromark-factory-space: 1.1.0 - micromark-util-character: 1.2.0 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - - micromark-factory-whitespace@1.1.0: - dependencies: - micromark-factory-space: 1.1.0 - micromark-util-character: 1.2.0 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - - micromark-util-character@1.2.0: - dependencies: - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - - micromark-util-chunked@1.1.0: - dependencies: - micromark-util-symbol: 1.1.0 - - micromark-util-classify-character@1.1.0: - dependencies: - micromark-util-character: 1.2.0 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - - micromark-util-combine-extensions@1.1.0: - dependencies: - micromark-util-chunked: 1.1.0 - micromark-util-types: 1.1.0 - - micromark-util-decode-numeric-character-reference@1.1.0: - dependencies: - micromark-util-symbol: 1.1.0 - - micromark-util-decode-string@1.1.0: - dependencies: - decode-named-character-reference: 1.0.2 - micromark-util-character: 1.2.0 - micromark-util-decode-numeric-character-reference: 1.1.0 - micromark-util-symbol: 1.1.0 - - micromark-util-encode@1.1.0: {} - - micromark-util-events-to-acorn@1.2.3: - dependencies: - '@types/acorn': 4.0.6 - '@types/estree': 1.0.6 - '@types/unist': 2.0.11 - estree-util-visit: 1.2.1 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - uvu: 0.5.6 - vfile-message: 3.1.4 - - micromark-util-html-tag-name@1.2.0: {} - - micromark-util-normalize-identifier@1.1.0: - dependencies: - micromark-util-symbol: 1.1.0 - - micromark-util-resolve-all@1.1.0: - dependencies: - micromark-util-types: 1.1.0 - - micromark-util-sanitize-uri@1.2.0: - dependencies: - micromark-util-character: 1.2.0 - micromark-util-encode: 1.1.0 - micromark-util-symbol: 1.1.0 - - micromark-util-subtokenize@1.1.0: - dependencies: - micromark-util-chunked: 1.1.0 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - uvu: 0.5.6 - - micromark-util-symbol@1.1.0: {} - - micromark-util-types@1.1.0: {} - - micromark@3.2.0: - dependencies: - '@types/debug': 4.1.12 - debug: 4.4.0 - decode-named-character-reference: 1.0.2 - micromark-core-commonmark: 1.1.0 - micromark-factory-space: 1.1.0 - micromark-util-character: 1.2.0 - micromark-util-chunked: 1.1.0 - micromark-util-combine-extensions: 1.1.0 - micromark-util-decode-numeric-character-reference: 1.1.0 - micromark-util-encode: 1.1.0 - micromark-util-normalize-identifier: 1.1.0 - micromark-util-resolve-all: 1.1.0 - micromark-util-sanitize-uri: 1.2.0 - micromark-util-subtokenize: 1.1.0 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - uvu: 0.5.6 - transitivePeerDependencies: - - supports-color - micromatch@4.0.8: dependencies: braces: 3.0.3 @@ -7516,68 +5193,23 @@ snapshots: dependencies: mime-db: 1.52.0 - mime@1.6.0: {} - mime@4.0.4: {} - mimic-fn@2.1.0: {} - minimatch@9.0.5: dependencies: brace-expansion: 2.0.1 - minimist@1.2.8: {} - - minipass-collect@1.0.2: - dependencies: - minipass: 3.3.6 - - minipass-flush@1.0.5: - dependencies: - minipass: 3.3.6 - - minipass-pipeline@1.2.4: - dependencies: - minipass: 3.3.6 - - minipass@3.3.6: - dependencies: - yallist: 4.0.0 - - minipass@5.0.0: {} - minipass@7.1.2: {} - minizlib@2.1.2: - dependencies: - minipass: 3.3.6 - yallist: 4.0.0 - minizlib@3.0.1: dependencies: minipass: 7.1.2 rimraf: 5.0.10 - mkdirp-classic@0.5.3: {} - - mkdirp@1.0.4: {} - mkdirp@3.0.1: {} - mlly@1.7.3: - dependencies: - acorn: 8.14.0 - pathe: 1.1.2 - pkg-types: 1.2.1 - ufo: 1.5.4 - - modern-ahocorasick@1.1.0: {} - - mri@1.2.0: {} - - mrmime@1.0.1: {} - - ms@2.0.0: {} + mrmime@1.0.1: + optional: true ms@2.1.3: {} @@ -7589,8 +5221,6 @@ snapshots: nanoid@3.3.8: {} - negotiator@0.6.3: {} - node-releases@2.0.18: {} normalize-package-data@5.0.0: @@ -7624,10 +5254,6 @@ snapshots: npm-package-arg: 10.1.0 semver: 7.6.3 - npm-run-path@4.0.1: - dependencies: - path-key: 3.1.1 - oauth-sign@0.9.0: {} oauth4webapi@2.17.0: {} @@ -7639,73 +5265,20 @@ snapshots: object-hash@3.0.0: {} - object-inspect@1.13.3: {} - - on-finished@2.4.1: - dependencies: - ee-first: 1.1.1 - once@1.4.0: dependencies: wrappy: 1.0.2 - onetime@5.1.2: - dependencies: - mimic-fn: 2.1.0 - openid-client@6.1.7: dependencies: jose: 5.9.6 oauth4webapi: 3.1.4 optional: true - ora@5.4.1: - dependencies: - bl: 4.1.0 - chalk: 4.1.2 - cli-cursor: 3.1.0 - cli-spinners: 2.9.2 - is-interactive: 1.0.0 - is-unicode-supported: 0.1.0 - log-symbols: 4.1.0 - strip-ansi: 6.0.1 - wcwidth: 1.0.1 - - outdent@0.8.0: {} - - p-limit@3.1.0: - dependencies: - yocto-queue: 0.1.0 - - p-locate@5.0.0: - dependencies: - p-limit: 3.1.0 - - p-map@4.0.0: - dependencies: - aggregate-error: 3.1.0 - package-json-from-dist@1.0.1: {} pako@0.2.9: {} - parse-entities@4.0.1: - dependencies: - '@types/unist': 2.0.11 - character-entities: 2.0.2 - character-entities-legacy: 3.0.0 - character-reference-invalid: 2.0.1 - decode-named-character-reference: 1.0.2 - is-alphanumerical: 2.0.1 - is-decimal: 2.0.1 - is-hexadecimal: 2.0.1 - - parse-ms@2.1.0: {} - - parseurl@1.3.3: {} - - path-exists@4.0.0: {} - path-key@3.1.1: {} path-parse@1.0.7: {} @@ -7715,8 +5288,6 @@ snapshots: lru-cache: 10.2.2 minipass: 7.1.2 - path-to-regexp@0.1.12: {} - pathe@1.1.2: {} peek-stream@1.1.3: @@ -7727,33 +5298,16 @@ snapshots: performance-now@2.1.0: {} - periscopic@3.1.0: - dependencies: - '@types/estree': 1.0.6 - estree-walker: 3.0.3 - is-reference: 3.0.3 - picocolors@1.1.1: {} picomatch@2.3.1: {} - pidtree@0.6.0: {} - pify@2.3.0: {} pirates@4.0.6: {} - pkg-types@1.2.1: - dependencies: - confbox: 0.1.8 - mlly: 1.7.3 - pathe: 1.1.2 - - possible-typed-array-names@1.0.0: {} - - postcss-discard-duplicates@5.1.0(postcss@8.4.49): - dependencies: - postcss: 8.4.49 + possible-typed-array-names@1.0.0: + optional: true postcss-import@15.1.0(postcss@8.4.49): dependencies: @@ -7774,39 +5328,6 @@ snapshots: optionalDependencies: postcss: 8.4.49 - postcss-modules-extract-imports@3.1.0(postcss@8.4.49): - dependencies: - postcss: 8.4.49 - - postcss-modules-local-by-default@4.1.0(postcss@8.4.49): - dependencies: - icss-utils: 5.1.0(postcss@8.4.49) - postcss: 8.4.49 - postcss-selector-parser: 7.0.0 - postcss-value-parser: 4.2.0 - - postcss-modules-scope@3.2.1(postcss@8.4.49): - dependencies: - postcss: 8.4.49 - postcss-selector-parser: 7.0.0 - - postcss-modules-values@4.0.0(postcss@8.4.49): - dependencies: - icss-utils: 5.1.0(postcss@8.4.49) - postcss: 8.4.49 - - postcss-modules@6.0.1(postcss@8.4.49): - dependencies: - generic-names: 4.0.0 - icss-utils: 5.1.0(postcss@8.4.49) - lodash.camelcase: 4.3.0 - postcss: 8.4.49 - postcss-modules-extract-imports: 3.1.0(postcss@8.4.49) - postcss-modules-local-by-default: 4.1.0(postcss@8.4.49) - postcss-modules-scope: 3.2.1(postcss@8.4.49) - postcss-modules-values: 4.0.0(postcss@8.4.49) - string-hash: 1.1.3 - postcss-nested@6.2.0(postcss@8.4.49): dependencies: postcss: 8.4.49 @@ -7817,11 +5338,6 @@ snapshots: cssesc: 3.0.0 util-deprecate: 1.0.2 - postcss-selector-parser@7.0.0: - dependencies: - cssesc: 3.0.0 - util-deprecate: 1.0.2 - postcss-value-parser@4.2.0: {} postcss@8.4.49: @@ -7832,10 +5348,6 @@ snapshots: prettier@2.8.8: {} - pretty-ms@7.0.1: - dependencies: - parse-ms: 2.1.0 - proc-log@3.0.0: {} process-nextick-args@2.0.1: {} @@ -7847,13 +5359,6 @@ snapshots: err-code: 2.0.3 retry: 0.12.0 - property-information@6.5.0: {} - - proxy-addr@2.0.7: - dependencies: - forwarded: 0.2.0 - ipaddr.js: 1.9.1 - psl@1.15.0: dependencies: punycode: 2.3.1 @@ -7863,11 +5368,6 @@ snapshots: end-of-stream: 1.4.4 once: 1.4.0 - pump@3.0.2: - dependencies: - end-of-stream: 1.4.4 - once: 1.4.0 - pumpify@1.5.1: dependencies: duplexify: 3.7.1 @@ -7876,23 +5376,10 @@ snapshots: punycode@2.3.1: {} - qs@6.13.0: - dependencies: - side-channel: 1.0.6 - qs@6.5.3: {} queue-microtask@1.2.3: {} - range-parser@1.2.1: {} - - raw-body@2.5.2: - dependencies: - bytes: 3.1.2 - http-errors: 2.0.0 - iconv-lite: 0.4.24 - unpipe: 1.0.0 - react-aria-components@1.5.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: '@internationalized/date': 3.6.0 @@ -8009,11 +5496,23 @@ snapshots: react: 19.0.0 react-dom: 19.0.0(react@19.0.0) react-router: 6.28.0(react@19.0.0) + optional: true react-router@6.28.0(react@19.0.0): dependencies: '@remix-run/router': 1.21.0 react: 19.0.0 + optional: true + + react-router@7.1.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + '@types/cookie': 0.6.0 + cookie: 1.0.2 + react: 19.0.0 + set-cookie-parser: 2.7.1 + turbo-stream: 2.4.0 + optionalDependencies: + react-dom: 19.0.0(react@19.0.0) react-stately@3.34.0(react@19.0.0): dependencies: @@ -8060,54 +5559,14 @@ snapshots: string_decoder: 1.1.1 util-deprecate: 1.0.2 - readable-stream@3.6.2: - dependencies: - inherits: 2.0.4 - string_decoder: 1.3.0 - util-deprecate: 1.0.2 - readdirp@3.6.0: dependencies: picomatch: 2.3.1 + readdirp@4.0.2: {} + regenerator-runtime@0.14.1: {} - remark-frontmatter@4.0.1: - dependencies: - '@types/mdast': 3.0.15 - mdast-util-frontmatter: 1.0.1 - micromark-extension-frontmatter: 1.1.1 - unified: 10.1.2 - - remark-mdx-frontmatter@1.1.1: - dependencies: - estree-util-is-identifier-name: 1.1.0 - estree-util-value-to-estree: 1.3.0 - js-yaml: 4.1.0 - toml: 3.0.0 - - remark-mdx@2.3.0: - dependencies: - mdast-util-mdx: 2.0.1 - micromark-extension-mdxjs: 1.0.1 - transitivePeerDependencies: - - supports-color - - remark-parse@10.0.2: - dependencies: - '@types/mdast': 3.0.15 - mdast-util-from-markdown: 1.3.1 - unified: 10.1.2 - transitivePeerDependencies: - - supports-color - - remark-rehype@10.1.0: - dependencies: - '@types/hast': 2.3.10 - '@types/mdast': 3.0.15 - mdast-util-to-hast: 12.3.0 - unified: 10.1.2 - remix-utils@7.7.0(@remix-run/node@2.15.0(typescript@5.7.2))(@remix-run/react@2.15.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2))(@remix-run/router@1.21.0)(react@19.0.0)(zod@3.23.8): dependencies: type-fest: 4.26.1 @@ -8141,21 +5600,12 @@ snapshots: tunnel-agent: 0.6.0 uuid: 3.4.0 - require-like@0.1.2: {} - - resolve.exports@2.0.3: {} - resolve@1.22.8: dependencies: is-core-module: 2.15.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - restore-cursor@3.1.0: - dependencies: - onetime: 5.1.2 - signal-exit: 3.0.7 - retry@0.12.0: {} reusify@1.0.4: {} @@ -8166,30 +5616,6 @@ snapshots: dependencies: glob: 10.4.5 - rollup@4.24.4: - dependencies: - '@types/estree': 1.0.6 - optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.24.4 - '@rollup/rollup-android-arm64': 4.24.4 - '@rollup/rollup-darwin-arm64': 4.24.4 - '@rollup/rollup-darwin-x64': 4.24.4 - '@rollup/rollup-freebsd-arm64': 4.24.4 - '@rollup/rollup-freebsd-x64': 4.24.4 - '@rollup/rollup-linux-arm-gnueabihf': 4.24.4 - '@rollup/rollup-linux-arm-musleabihf': 4.24.4 - '@rollup/rollup-linux-arm64-gnu': 4.24.4 - '@rollup/rollup-linux-arm64-musl': 4.24.4 - '@rollup/rollup-linux-powerpc64le-gnu': 4.24.4 - '@rollup/rollup-linux-riscv64-gnu': 4.24.4 - '@rollup/rollup-linux-s390x-gnu': 4.24.4 - '@rollup/rollup-linux-x64-gnu': 4.24.4 - '@rollup/rollup-linux-x64-musl': 4.24.4 - '@rollup/rollup-win32-arm64-msvc': 4.24.4 - '@rollup/rollup-win32-ia32-msvc': 4.24.4 - '@rollup/rollup-win32-x64-msvc': 4.24.4 - fsevents: 2.3.3 - rollup@4.28.1: dependencies: '@types/estree': 1.0.6 @@ -8219,10 +5645,6 @@ snapshots: dependencies: queue-microtask: 1.2.3 - sade@1.8.1: - dependencies: - mri: 1.2.0 - safe-buffer@5.1.2: {} safe-buffer@5.2.1: {} @@ -8235,33 +5657,6 @@ snapshots: semver@7.6.3: {} - send@0.19.0: - dependencies: - debug: 2.6.9 - depd: 2.0.0 - destroy: 1.2.0 - encodeurl: 1.0.2 - escape-html: 1.0.3 - etag: 1.8.1 - fresh: 0.5.2 - http-errors: 2.0.0 - mime: 1.6.0 - ms: 2.1.3 - on-finished: 2.4.1 - range-parser: 1.2.1 - statuses: 2.0.1 - transitivePeerDependencies: - - supports-color - - serve-static@1.16.2: - dependencies: - encodeurl: 2.0.0 - escape-html: 1.0.3 - parseurl: 1.3.3 - send: 0.19.0 - transitivePeerDependencies: - - supports-color - set-cookie-parser@2.7.1: {} set-function-length@1.2.2: @@ -8270,10 +5665,9 @@ snapshots: es-errors: 1.3.0 function-bind: 1.1.2 get-intrinsic: 1.2.4 - gopd: 1.0.1 + gopd: 1.2.0 has-property-descriptors: 1.0.2 - - setprototypeof@1.2.0: {} + optional: true shebang-command@2.0.0: dependencies: @@ -8281,15 +5675,6 @@ snapshots: shebang-regex@3.0.0: {} - side-channel@1.0.6: - dependencies: - call-bind: 1.0.8 - es-errors: 1.3.0 - get-intrinsic: 1.2.4 - object-inspect: 1.13.3 - - signal-exit@3.0.7: {} - signal-exit@4.1.0: {} source-map-js@1.2.1: {} @@ -8301,9 +5686,8 @@ snapshots: source-map@0.6.1: {} - source-map@0.7.4: {} - - space-separated-tokens@2.0.2: {} + source-map@0.7.4: + optional: true spdx-correct@3.2.0: dependencies: @@ -8331,20 +5715,12 @@ snapshots: safer-buffer: 2.1.2 tweetnacl: 0.14.5 - ssri@10.0.6: - dependencies: - minipass: 7.1.2 - - statuses@2.0.1: {} - stream-buffers@3.0.3: {} stream-shift@1.0.3: {} stream-slice@0.1.2: {} - string-hash@1.1.3: {} - string-width@4.2.3: dependencies: emoji-regex: 8.0.0 @@ -8361,15 +5737,6 @@ snapshots: dependencies: safe-buffer: 5.1.2 - string_decoder@1.3.0: - dependencies: - safe-buffer: 5.2.1 - - stringify-entities@4.0.4: - dependencies: - character-entities-html4: 2.1.0 - character-entities-legacy: 3.0.0 - strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -8378,16 +5745,8 @@ snapshots: dependencies: ansi-regex: 6.0.1 - strip-bom@3.0.0: {} - - strip-final-newline@2.0.0: {} - style-mod@4.1.2: {} - style-to-object@0.4.4: - dependencies: - inline-style-parser: 0.1.1 - sucrase@3.35.0: dependencies: '@jridgewell/gen-mapping': 0.3.5 @@ -8398,10 +5757,6 @@ snapshots: pirates: 4.0.6 ts-interface-checker: 0.1.13 - supports-color@7.2.0: - dependencies: - has-flag: 4.0.0 - supports-preserve-symlinks-flag@1.0.0: {} tailwind-merge@2.5.5: {} @@ -8441,30 +5796,6 @@ snapshots: transitivePeerDependencies: - ts-node - tar-fs@2.1.1: - dependencies: - chownr: 1.1.4 - mkdirp-classic: 0.5.3 - pump: 3.0.2 - tar-stream: 2.2.0 - - tar-stream@2.2.0: - dependencies: - bl: 4.1.0 - end-of-stream: 1.4.4 - fs-constants: 1.0.0 - inherits: 2.0.4 - readable-stream: 3.6.2 - - tar@6.2.1: - dependencies: - chownr: 2.0.0 - fs-minipass: 2.1.0 - minipass: 5.0.0 - minizlib: 2.1.2 - mkdirp: 1.0.4 - yallist: 4.0.0 - tar@7.4.3: dependencies: '@isaacs/fs-minipass': 4.0.1 @@ -8491,31 +5822,17 @@ snapshots: dependencies: is-number: 7.0.0 - toidentifier@1.0.1: {} - - toml@3.0.0: {} - tough-cookie@2.5.0: dependencies: psl: 1.15.0 punycode: 2.3.1 - trim-lines@3.0.1: {} - - trough@2.2.0: {} - ts-interface-checker@0.1.13: {} tsconfck@3.1.4(typescript@5.7.2): optionalDependencies: typescript: 5.7.2 - tsconfig-paths@4.2.0: - dependencies: - json5: 2.2.3 - minimist: 1.2.8 - strip-bom: 3.0.0 - tslib@2.6.2: {} tslib@2.8.1: {} @@ -8530,77 +5847,17 @@ snapshots: type-fest@4.26.1: {} - type-is@1.6.18: - dependencies: - media-typer: 0.3.0 - mime-types: 2.1.35 - typescript@5.7.2: {} - ufo@1.5.4: {} - - undici-types@6.20.0: {} + undici-types@6.20.0: + optional: true undici@6.21.0: {} undici@7.1.0: {} - unified@10.1.2: - dependencies: - '@types/unist': 2.0.11 - bail: 2.0.2 - extend: 3.0.2 - is-buffer: 2.0.5 - is-plain-obj: 4.1.0 - trough: 2.2.0 - vfile: 5.3.7 - - unique-filename@3.0.0: - dependencies: - unique-slug: 4.0.0 - - unique-slug@4.0.0: - dependencies: - imurmurhash: 0.1.4 - - unist-util-generated@2.0.1: {} - - unist-util-is@5.2.1: - dependencies: - '@types/unist': 2.0.11 - - unist-util-position-from-estree@1.1.2: - dependencies: - '@types/unist': 2.0.11 - - unist-util-position@4.0.4: - dependencies: - '@types/unist': 2.0.11 - - unist-util-remove-position@4.0.2: - dependencies: - '@types/unist': 2.0.11 - unist-util-visit: 4.1.2 - - unist-util-stringify-position@3.0.3: - dependencies: - '@types/unist': 2.0.11 - - unist-util-visit-parents@5.1.3: - dependencies: - '@types/unist': 2.0.11 - unist-util-is: 5.2.1 - - unist-util-visit@4.1.2: - dependencies: - '@types/unist': 2.0.11 - unist-util-is: 5.2.1 - unist-util-visit-parents: 5.1.3 - universalify@2.0.1: {} - unpipe@1.0.0: {} - update-browserslist-db@1.1.1(browserslist@4.24.2): dependencies: browserslist: 4.24.2 @@ -8629,18 +5886,10 @@ snapshots: is-generator-function: 1.0.10 is-typed-array: 1.1.13 which-typed-array: 1.1.16 - - utils-merge@1.0.1: {} + optional: true uuid@3.4.0: {} - uvu@0.5.6: - dependencies: - dequal: 2.0.3 - diff: 5.2.0 - kleur: 4.1.5 - sade: 1.8.1 - valibot@0.41.0(typescript@5.7.2): optionalDependencies: typescript: 5.7.2 @@ -8652,35 +5901,22 @@ snapshots: validate-npm-package-name@5.0.1: {} - vary@1.1.2: {} - verror@1.10.0: dependencies: assert-plus: 1.0.0 core-util-is: 1.0.2 extsprintf: 1.3.0 - vfile-message@3.1.4: - dependencies: - '@types/unist': 2.0.11 - unist-util-stringify-position: 3.0.3 - - vfile@5.3.7: - dependencies: - '@types/unist': 2.0.11 - is-buffer: 2.0.5 - unist-util-stringify-position: 3.0.3 - vfile-message: 3.1.4 - - vite-node@1.6.0(@types/node@22.10.1): + vite-node@3.0.0-beta.2(@types/node@22.10.1)(jiti@1.21.6)(yaml@2.6.1): dependencies: cac: 6.7.14 debug: 4.4.0 + es-module-lexer: 1.5.4 pathe: 1.1.2 - picocolors: 1.1.1 - vite: 5.4.11(@types/node@22.10.1) + vite: 6.0.3(@types/node@22.10.1)(jiti@1.21.6)(yaml@2.6.1) transitivePeerDependencies: - '@types/node' + - jiti - less - lightningcss - sass @@ -8689,6 +5925,8 @@ snapshots: - sugarss - supports-color - terser + - tsx + - yaml vite-plugin-babel@1.3.0(@babel/core@7.26.0)(vite@6.0.3(@types/node@22.10.1)(jiti@1.21.6)(yaml@2.6.1)): dependencies: @@ -8706,15 +5944,6 @@ snapshots: - supports-color - typescript - vite@5.4.11(@types/node@22.10.1): - dependencies: - esbuild: 0.21.5 - postcss: 8.4.49 - rollup: 4.24.4 - optionalDependencies: - '@types/node': 22.10.1 - fsevents: 2.3.3 - vite@6.0.3(@types/node@22.10.1)(jiti@1.21.6)(yaml@2.6.1): dependencies: esbuild: 0.24.0 @@ -8728,17 +5957,15 @@ snapshots: w3c-keyname@2.2.8: {} - wcwidth@1.0.1: - dependencies: - defaults: 1.0.4 - web-encoding@1.1.5: dependencies: util: 0.12.5 optionalDependencies: '@zxing/text-encoding': 0.9.0 + optional: true - web-streams-polyfill@3.3.3: {} + web-streams-polyfill@3.3.3: + optional: true which-typed-array@1.1.16: dependencies: @@ -8747,6 +5974,7 @@ snapshots: for-each: 0.3.3 gopd: 1.2.0 has-tostringtag: 1.0.2 + optional: true which@2.0.2: dependencies: @@ -8770,22 +5998,14 @@ snapshots: wrappy@1.0.2: {} - ws@7.5.10: {} - ws@8.18.0: {} xtend@4.0.2: {} yallist@3.1.1: {} - yallist@4.0.0: {} - yallist@5.0.0: {} yaml@2.6.1: {} - yocto-queue@0.1.0: {} - zod@3.23.8: {} - - zwitch@2.0.4: {} diff --git a/postcss.config.js b/postcss.config.js index 2e7af2b..7b75c83 100644 --- a/postcss.config.js +++ b/postcss.config.js @@ -1,6 +1,6 @@ export default { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -} + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/react-router.config.ts b/react-router.config.ts new file mode 100644 index 0000000..2a5dd84 --- /dev/null +++ b/react-router.config.ts @@ -0,0 +1,5 @@ +import type { Config } from '@react-router/dev/config'; + +export default { + basename: '/admin', +} satisfies Config; diff --git a/server/dev.mjs b/server/dev.mjs index f204e31..0a71ecc 100644 --- a/server/dev.mjs +++ b/server/dev.mjs @@ -1,12 +1,12 @@ // This is a polyglot entrypoint for Headplane when running in development // It does some silly magic to load the vite config, set some globals that // are required to function, and create the vite development server. -import { createServer } from 'vite' -import { env, exit } from 'node:process' -import { log } from './utils.mjs' +import { createServer } from 'vite'; +import { env, exit } from 'node:process'; +import { log } from './utils.mjs'; -log('DEVX', 'INFO', 'This script is only intended for development') -env.NODE_ENV = 'development' +log('DEVX', 'INFO', 'This script is only intended for development'); +env.NODE_ENV = 'development'; // The production entrypoint uses a global called "PREFIX" to determine // what route the application is being served at and a global called "BUILD" @@ -14,20 +14,20 @@ env.NODE_ENV = 'development' // the development server can function correctly and override the production // values. -log('DEVX', 'INFO', 'Creating Vite Development Server') +log('DEVX', 'INFO', 'Creating Vite Development Server'); const server = await createServer({ server: { - middlewareMode: true - } -}) + middlewareMode: true, + }, +}); // This entrypoint is defined in the documentation to load the server -const build = await server.ssrLoadModule('virtual:remix/server-build') +const build = await server.ssrLoadModule('virtual:react-router/server-build'); // We already handle this logic in the Vite configuration -global.PREFIX = server.config.base.slice(0, -1) -global.BUILD = build -global.MODE = 'development' -global.MIDDLEWARE = server.middlewares +global.PREFIX = server.config.base.slice(0, -1); +global.BUILD = build; +global.MODE = 'development'; +global.MIDDLEWARE = server.middlewares; -await import('./prod.mjs') +await import('./prod.mjs'); diff --git a/server/prod.mjs b/server/prod.mjs index 55bdf8a..c0d6734 100644 --- a/server/prod.mjs +++ b/server/prod.mjs @@ -4,69 +4,70 @@ // we can only need this file and a Node.js installation to run the server. // PREFIX is defined globally, see vite.config.ts -import { access, constants } from 'node:fs/promises' -import { createReadStream, existsSync, statSync } from 'node:fs' -import { createServer } from 'node:http' -import { join, resolve } from 'node:path' -import { env } from 'node:process' -import { log } from './utils.mjs' -import { getWss, registerWss } from './ws.mjs' +import { access, constants } from 'node:fs/promises'; +import { createReadStream, existsSync, statSync } from 'node:fs'; +import { createServer } from 'node:http'; +import { join, resolve } from 'node:path'; +import { env } from 'node:process'; +import { log } from './utils.mjs'; +import { getWss, registerWss } from './ws.mjs'; -log('SRVX', 'INFO', `Running with Node.js ${process.versions.node}`) +log('SRVX', 'INFO', `Running with Node.js ${process.versions.node}`); try { - await access('./node_modules/@remix-run', constants.F_OK | constants.R_OK) - log('SRVX', 'INFO', 'Found node_modules dependencies') + await access('./node_modules/@react-router', constants.F_OK | constants.R_OK); + log('SRVX', 'INFO', 'Found node_modules dependencies'); } catch (error) { - log('SRVX', 'ERROR', 'No node_modules found. Please run `pnpm install`') - log('SRVX', 'ERROR', error) - process.exit(1) + log('SRVX', 'ERROR', 'No node_modules found. Please run `pnpm install`'); + log('SRVX', 'ERROR', error); + process.exit(1); } const { createRequestHandler: remixRequestHandler, createReadableStreamFromReadable, - writeReadableStreamToWritable -} = await import('@remix-run/node') -const { default: mime } = await import('mime') + writeReadableStreamToWritable, +} = await import('@react-router/node'); +const { default: mime } = await import('mime'); -const port = env.PORT || 3000 -const host = env.HOST || '0.0.0.0' -const buildPath = env.BUILD_PATH || './build' -const baseDir = resolve(join(buildPath, 'client')) +const port = env.PORT || 3000; +const host = env.HOST || '0.0.0.0'; +const buildPath = env.BUILD_PATH || './build'; +const baseDir = resolve(join(buildPath, 'client')); if (!global.BUILD) { try { - await access(join(buildPath, 'server'), constants.F_OK | constants.R_OK) - log('SRVX', 'INFO', 'Found build directory') + await access(join(buildPath, 'server'), constants.F_OK | constants.R_OK); + log('SRVX', 'INFO', 'Found build directory'); } catch (error) { - const date = new Date().toISOString() - log('SRVX', 'ERROR', 'No build found. Please run `pnpm build`') - log('SRVX', 'ERROR', error) - process.exit(1) + const date = new Date().toISOString(); + log('SRVX', 'ERROR', 'No build found. Please run `pnpm build`'); + log('SRVX', 'ERROR', error); + process.exit(1); } // Because this is a dynamic import without an easily discernable path // we gain the "deoptimization" we want so that Vite doesn't bundle this - const build = await import(resolve(join(buildPath, 'server', 'index.js'))) - global.BUILD = build - global.MODE = 'production' + const build = await import(resolve(join(buildPath, 'server', 'index.js'))); + global.BUILD = build; + global.MODE = 'production'; } -const handler = remixRequestHandler(global.BUILD, global.MODE) +console.log(remixRequestHandler); +const handler = remixRequestHandler(global.BUILD, global.MODE); const http = createServer(async (req, res) => { - const url = new URL(`http://${req.headers.host}${req.url}`) + const url = new URL(`http://${req.headers.host}${req.url}`); if (global.MIDDLEWARE) { - await new Promise(resolve => { - global.MIDDLEWARE(req, res, resolve) - }) + await new Promise((resolve) => { + global.MIDDLEWARE(req, res, resolve); + }); } if (!url.pathname.startsWith(PREFIX)) { - res.writeHead(404) - res.end() - return + res.writeHead(404); + res.end(); + return; } // We need to handle an issue where say we are navigating to $PREFIX @@ -76,10 +77,10 @@ const http = createServer(async (req, res) => { // URL so that Remix can handle it correctly. if (url.pathname === PREFIX) { res.writeHead(302, { - Location: `${PREFIX}/` - }) - res.end() - return + Location: `${PREFIX}/`, + }); + res.end(); + return; } // Before we pass any requests to our Remix handler we need to check @@ -89,44 +90,44 @@ const http = createServer(async (req, res) => { // To optimize this, we send them as readable streams in the node // response and we also set headers for aggressive caching. if (url.pathname.startsWith(`${PREFIX}/assets/`)) { - const filePath = join(baseDir, url.pathname.replace(PREFIX, '')) - const exists = existsSync(filePath) - const stats = statSync(filePath) + const filePath = join(baseDir, url.pathname.replace(PREFIX, '')); + const exists = existsSync(filePath); + const stats = statSync(filePath); if (exists && stats.isFile()) { // Build assets are cache-bust friendly so we can cache them heavily if (req.url.startsWith('/build')) { - res.setHeader('Cache-Control', 'public, max-age=31536000, immutable') + res.setHeader('Cache-Control', 'public, max-age=31536000, immutable'); } // Send the file as a readable stream - const fileStream = createReadStream(filePath) - const type = mime.getType(filePath) + const fileStream = createReadStream(filePath); + const type = mime.getType(filePath); - res.setHeader('Content-Length', stats.size) - res.setHeader('Content-Type', type) - fileStream.pipe(res) - return + res.setHeader('Content-Length', stats.size); + res.setHeader('Content-Type', type); + fileStream.pipe(res); + return; } } // Handling the request - const controller = new AbortController() - res.on('close', () => controller.abort()) + const controller = new AbortController(); + res.on('close', () => controller.abort()); - const headers = new Headers() + const headers = new Headers(); for (const [key, value] of Object.entries(req.headers)) { - if (!value) continue + if (!value) continue; if (Array.isArray(value)) { for (const v of value) { - headers.append(key, v) + headers.append(key, v); } - continue + continue; } - headers.append(key, value) + headers.append(key, value); } const remixReq = new Request(url.href, { @@ -135,35 +136,36 @@ const http = createServer(async (req, res) => { signal: controller.signal, // If we have a body we set a duplex and we load the body - ...(req.method !== 'GET' && req.method !== 'HEAD' ? { - body: createReadableStreamFromReadable(req), - duplex: 'half' - } : {} - ) - }) + ...(req.method !== 'GET' && req.method !== 'HEAD' + ? { + body: createReadableStreamFromReadable(req), + duplex: 'half', + } + : {}), + }); // Pass our request to the Remix handler and get a response const response = await handler(remixReq, { - ws: getWss() - }) + ws: getWss(), + }); // Handle our response and reply - res.statusCode = response.status - res.statusMessage = response.statusText + res.statusCode = response.status; + res.statusMessage = response.statusText; for (const [key, value] of response.headers.entries()) { - res.appendHeader(key, value) + res.appendHeader(key, value); } if (response.body) { - await writeReadableStreamToWritable(response.body, res) - return + await writeReadableStreamToWritable(response.body, res); + return; } - res.end() -}) + res.end(); +}); -registerWss(http) +registerWss(http); http.listen(port, host, () => { - log('SRVX', 'INFO', `Running on ${host}:${port}`) -}) + log('SRVX', 'INFO', `Running on ${host}:${port}`); +}); diff --git a/server/utils.mjs b/server/utils.mjs index c18fdc2..0ac3468 100644 --- a/server/utils.mjs +++ b/server/utils.mjs @@ -1,4 +1,4 @@ export function log(topic, level, message) { - const date = new Date().toISOString() - console.log(`${date} (${level}) [${topic}] ${message}`) + const date = new Date().toISOString(); + console.log(`${date} (${level}) [${topic}] ${message}`); } diff --git a/server/ws.mjs b/server/ws.mjs index e15820f..7beb332 100644 --- a/server/ws.mjs +++ b/server/ws.mjs @@ -1,28 +1,28 @@ // The Websocket server is wholly responsible for ingesting messages from // Headplane agent instances (hopefully not more than 1 is running lol) -import { WebSocketServer } from 'ws' -import { log } from './utils.mjs' +import { WebSocketServer } from 'ws'; +import { log } from './utils.mjs'; -const wss = new WebSocketServer({ noServer: true }) +const wss = new WebSocketServer({ noServer: true }); wss.on('connection', (ws, req) => { // On connection the agent will send its NodeID via Headers // We store this for later use to validate and show on the UI - const nodeID = req.headers['x-headplane-ts-node-id'] + const nodeID = req.headers['x-headplane-ts-node-id']; if (!nodeID) { - ws.close(1008, 'ERR_NO_HP_TS_NODE_ID') - return + ws.close(1008, 'ERR_NO_HP_TS_NODE_ID'); + return; } -}) +}); export async function registerWss(server) { - log('SRVX', 'INFO', 'Registering Websocket Server') + log('SRVX', 'INFO', 'Registering Websocket Server'); server.on('upgrade', (request, socket, head) => { - wss.handleUpgrade(request, socket, head, ws => { - wss.emit('connection', ws, request) - }) - }) + wss.handleUpgrade(request, socket, head, (ws) => { + wss.emit('connection', ws, request); + }); + }); } export function getWss() { - return wss + return wss; } diff --git a/tailwind.config.ts b/tailwind.config.ts index 9b56ff1..965ab03 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -1,8 +1,8 @@ /* eslint-disable @typescript-eslint/naming-convention */ -import type { Config } from 'tailwindcss' -import colors from 'tailwindcss/colors' -import animate from 'tailwindcss-animate' -import aria from 'tailwindcss-react-aria-components' +import type { Config } from 'tailwindcss'; +import colors from 'tailwindcss/colors'; +import animate from 'tailwindcss-animate'; +import aria from 'tailwindcss-react-aria-components'; export default { content: ['./app/**/*.{js,jsx,ts,tsx}'], @@ -10,10 +10,10 @@ export default { container: { center: true, padding: { - 'DEFAULT': '1rem', - 'sm': '2rem', - 'lg': '4rem', - 'xl': '5rem', + DEFAULT: '1rem', + sm: '2rem', + lg: '4rem', + xl: '5rem', '2xl': '6rem', }, }, @@ -41,4 +41,4 @@ export default { }, }, plugins: [animate, aria], -} satisfies Config +} satisfies Config; diff --git a/tsconfig.json b/tsconfig.json index 4e698d8..fc23627 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,7 +9,7 @@ ], "compilerOptions": { "lib": ["DOM", "DOM.Iterable", "ES2022"], - "types": ["@remix-run/node", "vite/client"], + "types": ["vite/client"], "isolatedModules": true, "esModuleInterop": true, "jsx": "react-jsx", diff --git a/vite.config.ts b/vite.config.ts index 3f27b1f..c3b27b9 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,18 +1,18 @@ -import { vitePlugin as remix } from '@remix-run/dev' -import { defineConfig } from 'vite' -import babel from 'vite-plugin-babel' -import tsconfigPaths from 'vite-tsconfig-paths' -import { execSync } from 'node:child_process' +import { reactRouter } from '@react-router/dev/vite'; +import { defineConfig } from 'vite'; +import babel from 'vite-plugin-babel'; +import tsconfigPaths from 'vite-tsconfig-paths'; +import { execSync } from 'node:child_process'; -const prefix = process.env.__INTERNAL_PREFIX || '/admin' +const prefix = process.env.__INTERNAL_PREFIX || '/admin'; if (prefix.endsWith('/')) { - throw new Error('Prefix must not end with a slash') + throw new Error('Prefix must not end with a slash'); } // Load the version via git tags -const version = execSync('git describe --tags --always').toString().trim() +const version = execSync('git describe --tags --always').toString().trim(); if (!version) { - throw new Error('Unable to execute git describe') + throw new Error('Unable to execute git describe'); } export default defineConfig(({ isSsrBuild }) => { @@ -20,8 +20,8 @@ export default defineConfig(({ isSsrBuild }) => { // server/prod.mjs file that is built for production server bundle // We know the remix invoked command is vite:build if ( - process.env.NODE_ENV !== 'development' - && !process.argv.includes('vite:build') + process.env.NODE_ENV !== 'development' && + !process.argv.includes('vite:build') ) { return { build: { @@ -35,7 +35,7 @@ export default defineConfig(({ isSsrBuild }) => { banner: '#!/usr/bin/env node\n', }, external: (id) => id.startsWith('node:'), - } + }, }, define: { PREFIX: JSON.stringify(prefix), @@ -44,39 +44,29 @@ export default defineConfig(({ isSsrBuild }) => { alias: { stream: 'node:stream', crypto: 'node:crypto', - } - } - } + }, + }, + }; } - return ({ - base: `${prefix}/`, + return { + base: prefix, build: isSsrBuild ? { target: 'ES2022' } : {}, + define: { __VERSION__: JSON.stringify(version), }, + plugins: [ - remix({ - basename: `${prefix}/`, - future: { - v3_fetcherPersist: true, - v3_relativeSplatPath: true, - v3_throwAbortReason: true, - v3_lazyRouteDiscovery: true, - v3_singleFetch: true, - v3_routeConfig: true - }, - }), + reactRouter(), tsconfigPaths(), babel({ filter: /\.[jt]sx?$/, babelConfig: { presets: ['@babel/preset-typescript'], - plugins: [ - ['babel-plugin-react-compiler', {}], - ], + plugins: [['babel-plugin-react-compiler', {}]], }, }), ], - }) -}) + }; +});