diff --git a/frontend/package.json b/frontend/package.json index fbd5a3a9ea..dc560af101 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -58,14 +58,14 @@ "@radix-ui/react-toggle-group": "^1.1.11", "@radix-ui/react-tooltip": "^1.2.8", "@radix-ui/react-visually-hidden": "^1.2.3", - "@rivet-gg/cloud": "file:vendor/rivet-cloud.tgz", + "@rivet-gg/cloud": "https://pkg.pr.new/rivet-dev/cloud/@rivet-gg/cloud@7090780", "@rivet-gg/icons": "workspace:*", - "rivetkit": "*", "@rivetkit/engine-api-full": "workspace:*", "@sentry/react": "^8.55.0", "@sentry/vite-plugin": "^2.23.1", "@shikijs/langs": "^3.12.2", "@shikijs/transformers": "^3.12.2", + "@stepperize/react": "^5.1.8", "@tailwindcss/container-queries": "^0.1.1", "@tailwindcss/typography": "^0.5.16", "@tanstack/query-core": "^5.87.1", @@ -118,6 +118,7 @@ "react-inspector": "^6.0.2", "react-resizable-panels": "^2.1.9", "recharts": "^2.15.4", + "rivetkit": "*", "shiki": "^3.12.2", "sonner": "^1.7.4", "tailwind-merge": "^2.6.0", diff --git a/frontend/src/app/data-providers/cloud-data-provider.tsx b/frontend/src/app/data-providers/cloud-data-provider.tsx index 4633cef2fe..41e426807d 100644 --- a/frontend/src/app/data-providers/cloud-data-provider.tsx +++ b/frontend/src/app/data-providers/cloud-data-provider.tsx @@ -3,6 +3,7 @@ import { type Rivet, RivetClient } from "@rivet-gg/cloud"; import { type FetchFunction, fetcher } from "@rivetkit/engine-api-full/core"; import { infiniteQueryOptions, queryOptions } from "@tanstack/react-query"; import { cloudEnv } from "@/lib/env"; +import { queryClient } from "@/queries/global"; import { RECORDS_PER_PAGE } from "./default-data-provider"; import { type CreateNamespace, @@ -170,6 +171,7 @@ export const createOrganizationContext = ({ }; return { + organization, orgProjectNamespacesQueryOptions, currentOrgProjectNamespacesQueryOptions: (opts: { project: string; @@ -209,8 +211,7 @@ export const createOrganizationContext = ({ }) => { const response = await client.projects.create({ displayName: data.displayName, - name: data.nameId, - organizationId: organization, + org: organization, }); return response; @@ -233,6 +234,7 @@ export const createProjectContext = ({ } & ReturnType & ReturnType) => { return { + project, createNamespaceMutationOptions(opts: { onSuccess?: (data: Namespace) => void; }) { @@ -241,7 +243,6 @@ export const createProjectContext = ({ mutationKey: ["namespaces"], mutationFn: async (data: CreateNamespace) => { const response = await client.namespaces.create(project, { - name: data.name, displayName: data.displayName, org: organization, }); @@ -304,6 +305,25 @@ export const createProjectContext = ({ }, }; }, + accessTokenQueryOptions({ namespace }: { namespace: string }) { + return queryOptions({ + staleTime: 15 * 60 * 1000, // 15 minutes + gcTime: 15 * 60 * 1000, // 15 minutes + queryKey: [ + { organization, project, namespace }, + "access-token", + ], + queryFn: async ({ signal: abortSignal }) => { + const response = await client.namespaces.createAccessToken( + project, + namespace, + { org: organization }, + { abortSignal }, + ); + return response; + }, + }); + }, }; }; @@ -311,13 +331,11 @@ export const createNamespaceContext = ({ namespace, engineNamespaceName, engineNamespaceId, - engineToken, ...parent }: { namespace: string; engineNamespaceName: string; engineNamespaceId: string; - engineToken?: (() => string) | string; } & ReturnType & ReturnType & ReturnType) => { @@ -327,11 +345,41 @@ export const createNamespaceContext = ({ namespace: engineNamespaceName, namespaceId: engineNamespaceId, client: createEngineClient(cloudEnv().VITE_APP_CLOUD_ENGINE_URL, { - token: engineToken, + token: async () => { + const response = await queryClient.fetchQuery( + parent.accessTokenQueryOptions({ namespace }), + ); + + return response.token; + }, }), }), namespaceQueryOptions() { return parent.currentProjectNamespaceQueryOptions({ namespace }); }, + connectRunnerTokenQueryOptions() { + return queryOptions({ + staleTime: 5 * 60 * 1000, // 5 minutes + gcTime: 5 * 60 * 1000, // 5 minutes + queryKey: [ + { + namespace, + project: parent.project, + organization: parent.organization, + }, + "runners", + "connect", + ], + queryFn: async () => { + const f = parent.client.namespaces.createPublishableToken( + parent.project, + namespace, + { org: parent.organization }, + ); + const t = await f; + return t.token; + }, + }); + }, }; }; diff --git a/frontend/src/app/data-providers/engine-data-provider.tsx b/frontend/src/app/data-providers/engine-data-provider.tsx index d737caf0bf..18282e7656 100644 --- a/frontend/src/app/data-providers/engine-data-provider.tsx +++ b/frontend/src/app/data-providers/engine-data-provider.tsx @@ -1,5 +1,6 @@ import { type Rivet, RivetClient } from "@rivetkit/engine-api-full"; import { infiniteQueryOptions, queryOptions } from "@tanstack/react-query"; +import { getConfig, ls } from "@/components"; import { type Actor, ActorFeature, @@ -15,11 +16,11 @@ import { type DefaultDataProvider, RECORDS_PER_PAGE, } from "./default-data-provider"; -import { getConfig } from "@/components/lib/config"; + +const mightRequireAuth = __APP_TYPE__ === "engine"; export type CreateNamespace = { displayName: string; - name?: string; }; export type Namespace = { @@ -30,8 +31,8 @@ export type Namespace = { }; export function createClient( - baseUrl = getConfig().apiUrl, - opts: { token: (() => string) | string }, + baseUrl = engineEnv().VITE_APP_API_URL, + opts: { token: (() => string) | string | (() => Promise) }, ) { return new RivetClient({ baseUrl: () => baseUrl, @@ -43,7 +44,7 @@ export function createClient( export const createGlobalContext = (opts: { engineToken: (() => string) | string; }) => { - const client = createClient(getConfig().apiUrl, { + const client = createClient(engineEnv().VITE_APP_API_URL, { token: opts.engineToken, }); return { @@ -88,7 +89,7 @@ export const createGlobalContext = (opts: { mutationFn: async (data: CreateNamespace) => { const response = await client.namespaces.create({ displayName: data.displayName, - name: data.name || convertStringToId(data.displayName), + name: convertStringToId(data.displayName), }); return { @@ -133,7 +134,7 @@ export const createNamespaceContext = ({ retry: shouldRetryAllExpect403, throwOnError: noThrow, meta: { - mightRequireAuth: true, + mightRequireAuth, }, }); }, @@ -158,7 +159,7 @@ export const createNamespaceContext = ({ retry: shouldRetryAllExpect403, throwOnError: noThrow, meta: { - mightRequireAuth: true, + mightRequireAuth, }, }); }, @@ -187,7 +188,7 @@ export const createNamespaceContext = ({ retry: shouldRetryAllExpect403, throwOnError: noThrow, meta: { - mightRequireAuth: true, + mightRequireAuth, }, }); }, @@ -214,7 +215,7 @@ export const createNamespaceContext = ({ retry: shouldRetryAllExpect403, throwOnError: noThrow, meta: { - mightRequireAuth: true, + mightRequireAuth, }, }); }, @@ -289,7 +290,7 @@ export const createNamespaceContext = ({ retry: shouldRetryAllExpect403, throwOnError: noThrow, meta: { - mightRequireAuth: true, + mightRequireAuth, }, }); }, @@ -330,7 +331,7 @@ export const createNamespaceContext = ({ retry: shouldRetryAllExpect403, throwOnError: noThrow, meta: { - mightRequireAuth: true, + mightRequireAuth, }, }); }, @@ -354,7 +355,7 @@ export const createNamespaceContext = ({ throwOnError: noThrow, retry: shouldRetryAllExpect403, meta: { - mightRequireAuth: true, + mightRequireAuth, }, }; }, @@ -364,7 +365,7 @@ export const createNamespaceContext = ({ throwOnError: noThrow, retry: shouldRetryAllExpect403, meta: { - mightRequireAuth: true, + mightRequireAuth, }, mutationFn: async () => { await client.actorsDelete(actorId); @@ -375,14 +376,14 @@ export const createNamespaceContext = ({ return { ...dataProvider, - runnersQueryOptions(opts: { namespace: string }) { + runnersQueryOptions() { return infiniteQueryOptions({ - queryKey: [opts.namespace, "runners"], + queryKey: [{ namespace }, "runners"], initialPageParam: undefined as string | undefined, queryFn: async ({ pageParam, signal: abortSignal }) => { const data = await client.runners.list( { - namespace: opts.namespace, + namespace, cursor: pageParam ?? undefined, limit: RECORDS_PER_PAGE, }, @@ -399,7 +400,7 @@ export const createNamespaceContext = ({ select: (data) => data.pages.flatMap((page) => page.runners), retry: shouldRetryAllExpect403, meta: { - mightRequireAuth: true, + mightRequireAuth, }, }); }, @@ -430,7 +431,7 @@ export const createNamespaceContext = ({ retry: shouldRetryAllExpect403, throwOnError: noThrow, meta: { - mightRequireAuth: true, + mightRequireAuth, }, }); }, @@ -457,7 +458,7 @@ export const createNamespaceContext = ({ throwOnError: noThrow, retry: shouldRetryAllExpect403, meta: { - mightRequireAuth: true, + mightRequireAuth, }, }); }, @@ -482,7 +483,7 @@ export const createNamespaceContext = ({ }, retry: shouldRetryAllExpect403, meta: { - mightRequireAuth: true, + mightRequireAuth, }, }); }, @@ -507,6 +508,10 @@ export const createNamespaceContext = ({ }); return response; }, + retry: shouldRetryAllExpect403, + meta: { + mightRequireAuth, + }, }; }, runnerConfigsQueryOptions() { @@ -539,6 +544,25 @@ export const createNamespaceContext = ({ } return lastPage.pagination.cursor; }, + + retryDelay: 50_000, + retry: shouldRetryAllExpect403, + meta: { + mightRequireAuth, + }, + }); + }, + connectRunnerTokenQueryOptions() { + return queryOptions({ + staleTime: 1000, + gcTime: 1000, + queryKey: [{ namespace }, "runners", "connect"], + queryFn: async () => { + return ls.engineCredentials.get(getConfig().apiUrl) || ""; + }, + meta: { + mightRequireAuth, + }, }); }, }; diff --git a/frontend/src/app/dialogs/connect-railway-frame.tsx b/frontend/src/app/dialogs/connect-railway-frame.tsx index 21481b8a9a..edeca0e899 100644 --- a/frontend/src/app/dialogs/connect-railway-frame.tsx +++ b/frontend/src/app/dialogs/connect-railway-frame.tsx @@ -1,13 +1,42 @@ import { faQuestionCircle, faRailway, Icon } from "@rivet-gg/icons"; +import { useQuery } from "@tanstack/react-query"; import * as ConnectRailwayForm from "@/app/forms/connect-railway-form"; import { HelpDropdown } from "@/app/help-dropdown"; -import { Button, Flex, Frame } from "@/components"; +import { + Button, + type DialogContentProps, + DiscreteInput, + Frame, + Skeleton, +} from "@/components"; +import { useEngineCompatDataProvider } from "@/components/actors"; +import { defineStepper } from "@/components/ui/stepper"; +import { engineEnv } from "@/lib/env"; -export default function CreateProjectFrameContent() { +const { Stepper } = defineStepper( + { + id: "step-1", + title: "Deploy to Railway", + }, + { + id: "step-2", + title: "Set Environment Variables", + }, + { + id: "step-3", + title: "Confirm Connection", + }, +); + +interface ConnectRailwayFrameContentProps extends DialogContentProps {} + +export default function ConnectRailwayFrameContent({ + onClose, +}: ConnectRailwayFrameContentProps) { return ( {}} - defaultValues={{ name: "" }} + defaultValues={{ endpoint: "" }} > @@ -22,12 +51,134 @@ export default function CreateProjectFrameContent() { - - - - - + ); } + +function FormStepper({ onClose }: { onClose?: () => void }) { + return ( + + {({ methods }) => ( + <> + + {methods.all.map((step) => ( + methods.goTo(step.id)} + > + {step.title} + {methods.when(step.id, (step) => { + return ( + + {step.id === "step-1" && ( + <> +

+ Deploy any RivetKit app + to Railway. +

+

+ Or use our Railway + template to get started + quickly. +

+ + Deploy to Railway + + +

+ After deploying your app + to Railway, return here + to add the endpoint. +

+ + )} + {step.id === "step-2" && ( + + )} + {step.id === "step-3" && ( +
+ + +
+ )} + + + + +
+ ); + })} +
+ ))} +
+ + )} +
+ ); +} + +function EnvVariablesStep() { + const dataProvider = useEngineCompatDataProvider(); + + const { data, isLoading } = useQuery( + dataProvider.connectRunnerTokenQueryOptions(), + ); + + return ( + <> +

+ Set the following environment variables in your Railway project + settings. +

+
+ {__APP_TYPE__ === "engine" ? ( +
+ + +
+ ) : null} +
+ + {isLoading ? ( + + ) : ( + + )} +
+
+ + ); +} diff --git a/frontend/src/app/dialogs/connect-vercel-frame.tsx b/frontend/src/app/dialogs/connect-vercel-frame.tsx index 74c9e8b5af..18f9c18975 100644 --- a/frontend/src/app/dialogs/connect-vercel-frame.tsx +++ b/frontend/src/app/dialogs/connect-vercel-frame.tsx @@ -1,30 +1,43 @@ import { faQuestionCircle, faVercel, Icon } from "@rivet-gg/icons"; -import { useMutation } from "@tanstack/react-query"; import * as ConnectVercelForm from "@/app/forms/connect-vercel-form"; import { HelpDropdown } from "@/app/help-dropdown"; -import { Button, Flex, Frame } from "@/components"; -import { useEngineCompatDataProvider } from "@/components/actors"; +import { + Button, + type DialogContentProps, + Frame, +} from "@/components"; +import { defineStepper } from "@/components/ui/stepper"; -export default function CreateProjectFrameContent() { - const provider = useEngineCompatDataProvider(); +const { Stepper } = defineStepper( + { + id: "step-1", + title: "Select Vercel Plan", + }, + { + id: "step-2", + title: "Edit vercel.json", + }, + { + id: "step-3", + title: "Deploy to Vercel", + }, + { + id: "step-4", + title: "Confirm Connection", + }, +); - const { mutateAsync } = useMutation( - provider.createRunnerConfigMutationOptions(), - ); +interface CreateProjectFrameContentProps extends DialogContentProps {} +export default function CreateProjectFrameContent({ + onClose, +}: CreateProjectFrameContentProps) { return ( { - await mutateAsync({ - name: values.name, - config: { - serverless: { - url: values.endpoint, - }, - }, - }); - }} - defaultValues={{ name: "" }} + onSubmit={async () => {}} + mode="onChange" + revalidateMode="onChange" + defaultValues={{ plan: "hobby", endpoint: "" }} > @@ -40,17 +53,80 @@ export default function CreateProjectFrameContent() { - - - - - + - - - Add - - ); } + +function FormStepper({ onClose }: { onClose?: () => void }) { + return ( + + {({ methods }) => ( + <> + + {methods.all.map((step) => ( + methods.goTo(step.id)} + > + {step.title} + {methods.when(step.id, (step) => { + return ( + + {step.id === "step-1" && ( + + )} + {step.id === "step-2" && ( + + )} + {step.id === "step-3" && ( + <> +

+ Deploy your project to + Vercel using your + favorite method. After + deployment, return here + to add the endpoint. +

+ + )} + {step.id === "step-4" && ( +
+ + +
+ )} + + + + +
+ ); + })} +
+ ))} +
+ + )} +
+ ); +} diff --git a/frontend/src/app/forms/connect-railway-form.tsx b/frontend/src/app/forms/connect-railway-form.tsx index e84b4c5bc7..7d39d75cdb 100644 --- a/frontend/src/app/forms/connect-railway-form.tsx +++ b/frontend/src/app/forms/connect-railway-form.tsx @@ -1,31 +1,10 @@ -import { faCheck, faSpinnerThird, Icon } from "@rivet-gg/icons"; -import { useQuery } from "@tanstack/react-query"; -import { useParams } from "@tanstack/react-router"; -import { AnimatePresence, motion } from "framer-motion"; -import { type UseFormReturn, useFormContext } from "react-hook-form"; +import type { UseFormReturn } from "react-hook-form"; import z from "zod"; -import { - cn, - createSchemaForm, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, - Input, -} from "@/components"; -import { - useCloudDataProvider, - useEngineCompatDataProvider, -} from "@/components/actors"; +import * as ConnectVercelForm from "@/app/forms/connect-vercel-form"; +import { createSchemaForm } from "@/components"; export const formSchema = z.object({ - name: z - .string() - .max(16) - .refine((value) => value.trim() !== "" && value.trim() === value, { - message: "Name cannot be empty or contain whitespaces", - }), + endpoint: z.string().url(), }); export type FormValues = z.infer; @@ -37,86 +16,5 @@ export type SubmitHandler = ( const { Form, Submit, SetValue } = createSchemaForm(formSchema); export { Form, Submit, SetValue }; -export const Name = ({ className }: { className?: string }) => { - const { control } = useFormContext(); - return ( - ( - - Name - - - - - - )} - /> - ); -}; - -export function ConnectionCheck() { - const params = useParams({ - from: "/_context/_cloud/orgs/$organization/projects/$project/ns/$namespace", - }); - const { watch } = useFormContext(); - const name = watch("name"); - - const { data: namespace } = useQuery( - useCloudDataProvider().currentOrgProjectNamespaceQueryOptions(params), - ); - - const enabled = !!name && !!namespace?.access.engineNamespaceName; - - const { data } = useQuery({ - ...useEngineCompatDataProvider().runnerByNameQueryOptions({ - namespace: namespace?.access.engineNamespaceName || "", - runnerName: name, - }), - enabled, - refetchInterval: 1000, - }); - - const success = !!data; - - return ( - - {enabled ? ( - - {success ? ( - <> - {" "} - Runner successfully connected - - ) : ( - <> - {" "} - Waiting for runner to connect... - - )} - - ) : null} - - ); -} - -export { Preview } from "./connect-vercel-form"; +export const ConnectionCheck = ConnectVercelForm.ConnectionCheck; +export const Endpoint = ConnectVercelForm.Endpoint; diff --git a/frontend/src/app/forms/connect-vercel-form.tsx b/frontend/src/app/forms/connect-vercel-form.tsx index d23c28058b..9d8bc978c1 100644 --- a/frontend/src/app/forms/connect-vercel-form.tsx +++ b/frontend/src/app/forms/connect-vercel-form.tsx @@ -1,31 +1,32 @@ -import { faCopy, Icon } from "@rivet-gg/icons"; +import { faCheck, faSpinnerThird, Icon } from "@rivet-gg/icons"; import { useQuery } from "@tanstack/react-query"; -import { useParams } from "@tanstack/react-router"; +import { AnimatePresence, motion } from "framer-motion"; +import { useEffect, useState } from "react"; import { type UseFormReturn, useFormContext } from "react-hook-form"; import z from "zod"; import { Button, + CodeFrame, CodePreview, - CopyButton, + cn, createSchemaForm, FormControl, + FormDescription, FormField, FormItem, FormLabel, FormMessage, Input, - Label, - ScrollArea, + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, } from "@/components"; -import { useCloudDataProvider } from "@/components/actors"; +import { HelpDropdown } from "../help-dropdown"; export const formSchema = z.object({ - name: z - .string() - .max(16) - .refine((value) => value.trim() !== "" && value.trim() === value, { - message: "Name cannot be empty or contain whitespaces", - }), + plan: z.string(), endpoint: z.string().url(), }); @@ -38,22 +39,36 @@ export type SubmitHandler = ( const { Form, Submit, SetValue } = createSchemaForm(formSchema); export { Form, Submit, SetValue }; -export const Name = ({ className }: { className?: string }) => { +export const Plan = ({ className }: { className?: string }) => { const { control } = useFormContext(); return ( ( - Name + Vercel Plan - + + + Your Vercel plan determines the configuration required + to properly run your Rivet Engine. + )} @@ -61,7 +76,54 @@ export const Name = ({ className }: { className?: string }) => { ); }; -export const Endpoint = ({ className }: { className?: string }) => { +const PLAN_TO_MAX_DURATION: Record = { + hobby: 60, + pro: 300, + enterprise: 900, +}; + +const code = ({ plan }: { plan: string }) => + `{ + "$schema": "https://openapi.vercel.sh/vercel.json", + "fluid": false, // [!code highlight] + "functions": { + "**": { + "maxDuration": ${PLAN_TO_MAX_DURATION[plan] || 60}, // [!code highlight] + }, + }, +}`; + +export const Json = () => { + const { watch } = useFormContext(); + + const plan = watch("plan"); + return ( +
+ + + + + Max Duration - The maximum execution time of your + serverless functions. +
+ Disable Fluid Compute - Rivet has its own intelligent + load balancing mechanism. +
+
+ ); +}; + +export const Endpoint = ({ + className, + placeholder, +}: { + className?: string; + placeholder?: string; +}) => { const { control } = useFormContext(); return ( { name="endpoint" render={({ field }) => ( - - Functions Endpoint - + Endpoint @@ -86,59 +148,89 @@ export const Endpoint = ({ className }: { className?: string }) => { ); }; -const code = ({ - token, - name, -}: { - token: string; - name: string; -}) => `import { registry } from "./registry"; - -registry.runServer({ - token: "${token}", - name: "${name}", -});`; - -export function Preview() { - const params = useParams({ - from: "/_context/_cloud/orgs/$organization/projects/$project/ns/$namespace", +export function ConnectionCheck() { + const { watch } = useFormContext(); + const endpoint = watch("endpoint"); + const enabled = !!endpoint && z.string().url().safeParse(endpoint).success; + + const { data } = useQuery({ + queryKey: ["vercel-endpoint-check", endpoint], + queryFn: async () => { + try { + const url = new URL("/health", endpoint); + const response = await fetch(url); + if (!response.ok) { + throw new Error("Failed to connect"); + } + return response.json(); + } catch { + const url = new URL("/api/rivet/health", endpoint); + const response = await fetch(url); + if (!response.ok) { + throw new Error("Failed to connect"); + } + return response.json(); + } + }, + enabled, + refetchInterval: 1000, }); - const { data } = useQuery( - useCloudDataProvider().currentOrgProjectNamespaceQueryOptions(params), - ); - const { watch } = useFormContext(); - const name = watch("name"); + const success = !!data; + return ( -
- -
- - ", - name, - })} - /> - - - ", - name, - })} + + {enabled ? ( + - - -
-
+ {success ? ( + <> + {" "} + Runner successfully connected + + ) : ( +
+
+ {" "} + Waiting for Runner to connect... +
+ +
+ )} + + ) : null} + + ); +} + +export function NeedHelp() { + const [open, setOpen] = useState(false); + + useEffect(() => { + const timeout = setTimeout(() => { + setOpen(true); + }, 10000); + return () => clearTimeout(timeout); + }, []); + + if (!open) return null; + + return ( + + + ); } diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index e2a0faea92..a2a6ed20b0 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -397,12 +397,12 @@ const Subnav = () => {
{__APP_TYPE__ === "engine" ? ( - Runners + Connect ) : null}
diff --git a/frontend/src/components/actors/guard-connectable-inspector.tsx b/frontend/src/components/actors/guard-connectable-inspector.tsx index 4f1100da5a..37ee097dd5 100644 --- a/frontend/src/components/actors/guard-connectable-inspector.tsx +++ b/frontend/src/components/actors/guard-connectable-inspector.tsx @@ -1,7 +1,13 @@ import { faPowerOff, faSpinnerThird, Icon } from "@rivet-gg/icons"; import { useMutation, useQuery, useSuspenseQuery } from "@tanstack/react-query"; import { useMatch } from "@tanstack/react-router"; -import { createContext, type ReactNode, useContext, useMemo } from "react"; +import { + createContext, + type ReactNode, + useContext, + useMemo, + useState, +} from "react"; import { useInspectorCredentials } from "@/app/credentials-context"; import { createInspectorActorContext } from "@/queries/actor-inspector"; import { DiscreteCopyButton } from "../copy-area"; @@ -33,6 +39,12 @@ export function GuardConnectableInspector({ } = useQuery({ ...useDataProvider().actorQueryOptions(actorId), refetchInterval: 1000, + select: (data) => ({ + destroyedAt: data.destroyedAt, + sleepingAt: data.sleepingAt, + pendingAllocationAt: data.pendingAllocationAt, + startedAt: data.startedAt, + }), }); if (destroyedAt) { @@ -126,7 +138,7 @@ function ActorInspectorProvider({ children }: { children: ReactNode }) { } function useActorRunner({ actorId }: { actorId: ActorId }) { - const { data: actor } = useSuspenseQuery( + const { data: actor, isLoading } = useSuspenseQuery( useDataProvider().actorQueryOptions(actorId), ); @@ -142,19 +154,29 @@ function useActorRunner({ actorId }: { actorId: ActorId }) { throw new Error("Actor is missing required fields"); } - const { data: runner } = useQuery({ + const { + data: runner, + isLoading: isLoadingRunner, + isSuccess, + } = useQuery({ ...useEngineCompatDataProvider().runnerByNameQueryOptions({ runnerName: actor.runner, namespace: match.params.namespace, }), + retryDelay: 10_000, refetchInterval: 1000, }); - return { actor, runner }; + return { + actor, + runner, + isLoading: isLoading || isLoadingRunner, + isSuccess, + }; } function useActorEngineContext({ actorId }: { actorId: ActorId }) { - const { actor, runner } = useActorRunner({ actorId }); + const { actor, runner, isLoading } = useActorRunner({ actorId }); const actorContext = useMemo(() => { return createInspectorActorContext({ @@ -164,7 +186,7 @@ function useActorEngineContext({ actorId }: { actorId: ActorId }) { }); }, [runner?.metadata?.inspectorToken]); - return { actorContext, actor, runner }; + return { actorContext, actor, runner, isLoading }; } function ActorEngineProvider({ @@ -174,7 +196,9 @@ function ActorEngineProvider({ actorId: ActorId; children: ReactNode; }) { - const { actorContext, actor, runner } = useActorEngineContext({ actorId }); + const { actorContext, actor, runner } = useActorEngineContext({ + actorId, + }); if (!runner || !actor.runner) { return ( @@ -207,6 +231,7 @@ function NoRunnerInfo({ runner }: { runner: string }) { {runner}

+

Will retry automatically in the background.

); } @@ -232,9 +257,11 @@ function WakeUpActorButton({ actorId }: { actorId: ActorId }) { } function AutoWakeUpActor({ actorId }: { actorId: ActorId }) { - const { runner, actor, actorContext } = useActorEngineContext({ actorId }); + const { runner, actor, actorContext } = useActorEngineContext({ + actorId, + }); - const { isPending } = useQuery( + useQuery( actorContext.actorAutoWakeUpQueryOptions(actorId, { enabled: !!runner, }), @@ -242,12 +269,12 @@ function AutoWakeUpActor({ actorId }: { actorId: ActorId }) { if (!runner) return ; - return isPending ? ( + return (
Waiting for Actor to wake...
- ) : null; + ); } diff --git a/frontend/src/components/code-preview/code-preview.tsx b/frontend/src/components/code-preview/code-preview.tsx index 2565ca003f..2d1b52c0a2 100644 --- a/frontend/src/components/code-preview/code-preview.tsx +++ b/frontend/src/components/code-preview/code-preview.tsx @@ -1,3 +1,4 @@ +import { transformerNotationHighlight } from "@shikijs/transformers"; import { useEffect, useMemo, useRef, useState } from "react"; import { createHighlighterCore, @@ -8,9 +9,14 @@ import { import { Skeleton } from "../ui/skeleton"; import theme from "./theme.json"; +const langs = { + typescript: () => import("@shikijs/langs/typescript"), + json: () => import("@shikijs/langs/json"), +}; + interface CodePreviewProps { code: string; - language: "typescript"; + language: keyof typeof langs; className?: string; } @@ -24,7 +30,7 @@ export function CodePreview({ className, code, language }: CodePreviewProps) { async function createHighlighter() { highlighter.current ??= await createHighlighterCore({ themes: [theme as ThemeInput], - langs: [import("@shikijs/langs/typescript")], + langs: [await langs[language]()], engine: createOnigurumaEngine(import("shiki/wasm")), }); } @@ -36,7 +42,7 @@ export function CodePreview({ className, code, language }: CodePreviewProps) { return () => { highlighter.current?.dispose(); }; - }, []); + }, [language]); const result = useMemo( () => @@ -45,6 +51,7 @@ export function CodePreview({ className, code, language }: CodePreviewProps) { : (highlighter.current?.codeToHtml(code, { lang: language, theme: theme.name, + transformers: [transformerNotationHighlight()], }) as TrustedHTML), [isLoading, code, language], ); diff --git a/frontend/src/components/code.tsx b/frontend/src/components/code.tsx index a793155b10..c06c5591de 100644 --- a/frontend/src/components/code.tsx +++ b/frontend/src/components/code.tsx @@ -104,7 +104,7 @@ export const CodeFrame = ({ }: CodeFrameProps) => { return (
-
+
{children ? cloneElement(children, { escaped: true }) diff --git a/frontend/src/components/copy-area.tsx b/frontend/src/components/copy-area.tsx index d0c8af7ffb..37b737af27 100644 --- a/frontend/src/components/copy-area.tsx +++ b/frontend/src/components/copy-area.tsx @@ -1,7 +1,7 @@ "use client"; import { Slot } from "@radix-ui/react-slot"; -import { faCopy, Icon } from "@rivet-gg/icons"; +import { faCopy, faEye, faEyeSlash, Icon } from "@rivet-gg/icons"; import { type ComponentProps, forwardRef, @@ -139,7 +139,7 @@ export const DiscreteCopyButton = forwardRef< type="button" variant="ghost" size={props.size} - className={cn(props.className, "max-w-full min-w-0")} + className={cn("max-w-full min-w-0", props.className)} endIcon={ ); } + +export function DiscreteInput({ + value, + show, +}: { + value: string; + show?: boolean; +}) { + const [showState, setShowState] = useState(!!show); + + const finalShow = showState || !!show; + return ( +
+ + + + {!show ? ( + + ) : null} +
+ ); +} diff --git a/frontend/src/components/lib/config.ts b/frontend/src/components/lib/config.ts index 2131a0d0fc..b4702f10dc 100644 --- a/frontend/src/components/lib/config.ts +++ b/frontend/src/components/lib/config.ts @@ -23,16 +23,16 @@ export const ConfigContext = createContext({ export const useConfig = () => useContext(ConfigContext); export const ConfigProvider = ConfigContext.Provider; -const getApiEndpoint = (apiEndpoint: string) => { - if (apiEndpoint === "__AUTO__") { - if (location.hostname.startsWith("hub.")) { +export const getApiEndpoint = (apiEndpoint: string) => { + if (typeof window !== "undefined" && apiEndpoint === "__AUTO__") { + if (window.location.hostname.startsWith("hub.")) { // Connect to the corresponding API endpoint - return `https://${location.hostname.replace("hub.", "api.")}`; + return `https://${window.location.hostname.replace("hub.", "api.")}`; } // Default to staging servers for all other endpoints return "https://api.staging2.gameinc.io"; - } else if (apiEndpoint === "__SAME__") { - return location.origin; + } else if (typeof window !== "undefined" && apiEndpoint === "__SAME__") { + return window.location.origin; } return apiEndpoint; }; diff --git a/frontend/src/components/lib/create-schema-form.tsx b/frontend/src/components/lib/create-schema-form.tsx index 9f314696ac..f67527d2c8 100644 --- a/frontend/src/components/lib/create-schema-form.tsx +++ b/frontend/src/components/lib/create-schema-form.tsx @@ -18,6 +18,8 @@ import { Button, type ButtonProps, Form } from "@/components"; interface FormProps extends Omit, "onSubmit"> { onSubmit: SubmitHandler; + mode?: UseFormProps["mode"]; + revalidateMode?: UseFormProps["reValidateMode"]; defaultValues: DefaultValues; errors?: UseFormProps["errors"]; values?: FormValues; @@ -35,6 +37,8 @@ export const createSchemaForm = ( return { Form: ({ defaultValues, + mode, + revalidateMode = "onSubmit", values, children, onSubmit, @@ -42,8 +46,9 @@ export const createSchemaForm = ( ...props }: FormProps>) => { const form = useForm>({ - reValidateMode: "onSubmit", resolver: zodResolver(schema), + mode, + reValidateMode: revalidateMode, defaultValues, values, errors, diff --git a/frontend/src/components/steps.tsx b/frontend/src/components/steps.tsx index c891359a01..3932d69ea5 100644 --- a/frontend/src/components/steps.tsx +++ b/frontend/src/components/steps.tsx @@ -19,7 +19,7 @@ export function Steps({ className, children }: StepsProps) { } interface StepProps { - children: React.ReactNode; + children?: React.ReactNode; className?: string; title: string; } @@ -29,7 +29,7 @@ export function Step({ children, className, title }: StepProps) { <>

diff --git a/frontend/src/components/theme.css b/frontend/src/components/theme.css index b13bd89634..6faea737e8 100644 --- a/frontend/src/components/theme.css +++ b/frontend/src/components/theme.css @@ -60,3 +60,13 @@ --chart-4: 280 65% 60%; --chart-5: 340 75% 55%; } + +.shiki .line { + display: inline-block; + width: 100%;; + @apply px-2; +} + +.shiki .highlighted { + @apply bg-secondary/50; +} \ No newline at end of file diff --git a/frontend/src/components/ui/button.tsx b/frontend/src/components/ui/button.tsx index 17736a8daa..61095b72bb 100644 --- a/frontend/src/components/ui/button.tsx +++ b/frontend/src/components/ui/button.tsx @@ -52,7 +52,7 @@ export interface ButtonProps isLoading?: boolean; startIcon?: React.ReactElement; endIcon?: React.ReactElement; - onClick?: (e?: React.MouseEvent) => void; + onClick?: (e: React.MouseEvent) => void; } const Button = React.forwardRef( diff --git a/frontend/src/components/ui/stepper.tsx b/frontend/src/components/ui/stepper.tsx new file mode 100644 index 0000000000..92127ec41b --- /dev/null +++ b/frontend/src/components/ui/stepper.tsx @@ -0,0 +1,548 @@ +import { Slot } from "@radix-ui/react-slot"; +import * as Stepperize from "@stepperize/react"; +import { cva, type VariantProps } from "class-variance-authority"; +import * as React from "react"; +import { cn } from "../lib/utils"; +import { Button } from "./button"; + +const StepperContext = React.createContext(null); + +const useStepperProvider = (): Stepper.ConfigProps => { + const context = React.useContext(StepperContext); + if (!context) { + throw new Error("useStepper must be used within a StepperProvider."); + } + return context; +}; + +const defineStepper = ( + ...steps: Steps +): Stepper.DefineProps => { + const { Scoped, useStepper, ...rest } = Stepperize.defineStepper(...steps); + + const StepperContainer = ({ + children, + className, + ...props + }: Omit, "children"> & { + children: + | React.ReactNode + | ((props: { + methods: Stepperize.Stepper; + }) => React.ReactNode); + }) => { + const methods = useStepper(); + + return ( +
+ {typeof children === "function" + ? children({ methods }) + : children} +
+ ); + }; + + return { + ...rest, + useStepper, + Stepper: { + Provider: ({ + variant = "horizontal", + labelOrientation = "horizontal", + tracking = false, + children, + className, + ...props + }) => { + return ( + + + + {children} + + + + ); + }, + Navigation: ({ + children, + "aria-label": ariaLabel = "Stepper Navigation", + ...props + }) => { + const { variant } = useStepperProvider(); + return ( + + ); + }, + Step: ({ children, className, icon, ...props }) => { + const { variant, labelOrientation } = useStepperProvider(); + const { current } = useStepper(); + + const utils = rest.utils; + const steps = rest.steps; + + const stepIndex = utils.getIndex(props.of); + const step = steps[stepIndex]; + const currentIndex = utils.getIndex(current.id); + + const isLast = utils.getLast().id === props.of; + const isActive = current.id === props.of; + + const dataState = getStepState(currentIndex, stepIndex); + const childMap = useStepChildren(children); + + const title = childMap.get("title"); + const description = childMap.get("description"); + const panel = childMap.get("panel"); + + if (variant === "circle") { + return ( +
  • + +
    + {title} + {description} +
    +
  • + ); + } + + return ( + <> +
  • + + {variant === "horizontal" && + labelOrientation === "vertical" && ( + + )} +
    + {title} + {description} +
    +
  • + + {variant === "horizontal" && + labelOrientation === "horizontal" && ( + + )} + + {variant === "vertical" && ( +
    + {!isLast && ( +
    + +
    + )} +
    + {panel} +
    +
    + )} + + ); + }, + Title, + Description, + Panel: ({ children, asChild, ...props }) => { + const Comp = asChild ? Slot : "div"; + const { tracking } = useStepperProvider(); + + return ( + scrollIntoStepperPanel(node, tracking)} + {...props} + > + {children} + + ); + }, + Controls: ({ children, className, asChild, ...props }) => { + const Comp = asChild ? Slot : "div"; + return ( + + {children} + + ); + }, + }, + }; +}; + +const Title = ({ + children, + className, + asChild, + ...props +}: React.ComponentProps<"h4"> & { asChild?: boolean }) => { + const Comp = asChild ? Slot : "h4"; + + return ( + + {children} + + ); +}; + +const Description = ({ + children, + className, + asChild, + ...props +}: React.ComponentProps<"p"> & { asChild?: boolean }) => { + const Comp = asChild ? Slot : "p"; + + return ( + + {children} + + ); +}; + +const StepperSeparator = ({ + orientation, + isLast, + labelOrientation, + state, + disabled, +}: { + isLast: boolean; + state: string; + disabled?: boolean; +} & VariantProps) => { + if (isLast) { + return null; + } + return ( +
    + ); +}; + +const CircleStepIndicator = ({ + currentStep, + totalSteps, + size = 80, + strokeWidth = 6, +}: Stepper.CircleStepIndicatorProps) => { + const radius = (size - strokeWidth) / 2; + const circumference = radius * 2 * Math.PI; + const fillPercentage = (currentStep / totalSteps) * 100; + const dashOffset = circumference - (circumference * fillPercentage) / 100; + return ( +
    + + Step Indicator + + + +
    + + {currentStep} of {totalSteps} + +
    +
    + ); +}; + +const classForNavigationList = cva("flex gap-2", { + variants: { + variant: { + horizontal: "flex-row items-center justify-between", + vertical: "flex-col", + circle: "flex-row items-center justify-between", + }, + }, +}); + +const classForSeparator = cva( + [ + "bg-muted", + "data-[state=completed]:bg-primary data-[disabled]:opacity-50", + "transition-all duration-300 ease-in-out", + ], + { + variants: { + orientation: { + horizontal: "h-0.5 flex-1", + vertical: "h-full w-0.5", + }, + labelOrientation: { + vertical: + "absolute left-[calc(50%+30px)] right-[calc(-50%+20px)] top-5 block shrink-0", + }, + }, + }, +); + +function scrollIntoStepperPanel( + node: HTMLDivElement | null, + tracking?: boolean, +) { + if (tracking) { + node?.scrollIntoView({ behavior: "smooth", block: "center" }); + } +} + +const useStepChildren = (children: React.ReactNode) => { + return React.useMemo(() => extractChildren(children), [children]); +}; + +const extractChildren = (children: React.ReactNode) => { + const childrenArray = React.Children.toArray(children); + const map = new Map(); + + for (const child of childrenArray) { + if (React.isValidElement(child)) { + if (child.type === Title) { + map.set("title", child); + } else if (child.type === Description) { + map.set("description", child); + } else { + map.set("panel", child); + } + } + } + + return map; +}; + +const onStepKeyDown = ( + e: React.KeyboardEvent, + nextStep: Stepperize.Step, + prevStep: Stepperize.Step, +) => { + const { key } = e; + const directions = { + next: ["ArrowRight", "ArrowDown"], + prev: ["ArrowLeft", "ArrowUp"], + }; + + if (directions.next.includes(key) || directions.prev.includes(key)) { + const direction = directions.next.includes(key) ? "next" : "prev"; + const step = direction === "next" ? nextStep : prevStep; + + if (!step) { + return; + } + + const stepElement = document.getElementById(`step-${step.id}`); + if (!stepElement) { + return; + } + + const isActive = + stepElement.parentElement?.getAttribute("data-state") !== + "inactive"; + if (isActive || direction === "prev") { + stepElement.focus(); + } + } +}; + +const getStepState = (currentIndex: number, stepIndex: number) => { + if (currentIndex === stepIndex) { + return "active"; + } + if (currentIndex > stepIndex) { + return "completed"; + } + return "inactive"; +}; + +namespace Stepper { + export type StepperVariant = "horizontal" | "vertical" | "circle"; + export type StepperLabelOrientation = "horizontal" | "vertical"; + + export type ConfigProps = { + variant?: StepperVariant; + labelOrientation?: StepperLabelOrientation; + tracking?: boolean; + }; + + export type DefineProps = Omit< + Stepperize.StepperReturn, + "Scoped" + > & { + Stepper: { + Provider: ( + props: Omit, "children"> & + Omit, "children"> & + Stepper.ConfigProps & { + children: + | React.ReactNode + | ((props: { + methods: Stepperize.Stepper; + }) => React.ReactNode); + }, + ) => React.ReactElement; + Navigation: ( + props: React.ComponentProps<"nav">, + ) => React.ReactElement; + Step: ( + props: React.ComponentProps<"button"> & { + of: Stepperize.Get.Id; + icon?: React.ReactNode; + }, + ) => React.ReactElement; + Title: (props: AsChildProps<"h4">) => React.ReactElement; + Description: (props: AsChildProps<"p">) => React.ReactElement; + Panel: (props: AsChildProps<"div">) => React.ReactElement; + Controls: (props: AsChildProps<"div">) => React.ReactElement; + }; + }; + + export type CircleStepIndicatorProps = { + currentStep: number; + totalSteps: number; + size?: number; + strokeWidth?: number; + }; +} + +type AsChildProps = React.ComponentProps & { + asChild?: boolean; +}; + +export { defineStepper }; diff --git a/frontend/src/index.css b/frontend/src/index.css index d2f8500d63..1822e622ca 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -84,6 +84,8 @@ /* biome-ignore lint/correctness/noUnknownFunction: tailwind functions */ --shiki-token-punctuation: theme("colors.zinc.200"); + --spacing: 0.25rem; + ::-webkit-scrollbar { @apply w-2.5 h-2.5; diff --git a/frontend/src/lib/env.ts b/frontend/src/lib/env.ts index b4c798b7e5..e7721c12fd 100644 --- a/frontend/src/lib/env.ts +++ b/frontend/src/lib/env.ts @@ -1,7 +1,10 @@ import z from "zod"; +import { getApiEndpoint } from "../components/lib/config"; export const commonEnvSchema = z.object({ - VITE_APP_API_URL: z.string(), + VITE_APP_API_URL: z.string().transform((url) => { + return getApiEndpoint(url); + }), VITE_APP_ASSETS_URL: z.string().url(), VITE_APP_POSTHOG_API_KEY: z.string().optional(), VITE_APP_POSTHOG_API_HOST: z.string().url().optional(), @@ -19,7 +22,9 @@ export const engineEnv = () => commonEnvSchema.parse(import.meta.env); export const cloudEnvSchema = commonEnvSchema.merge( z.object({ - VITE_APP_API_URL: z.string().url(), + VITE_APP_API_URL: z.string().transform((url) => { + return getApiEndpoint(url); + }), VITE_APP_CLOUD_ENGINE_URL: z.string().url(), VITE_APP_CLOUD_API_URL: z.string().url(), VITE_APP_CLERK_PUBLISHABLE_KEY: z.string(), diff --git a/frontend/src/queries/utils.ts b/frontend/src/queries/utils.ts index bf27f9ae62..65b18a8d5b 100644 --- a/frontend/src/queries/utils.ts +++ b/frontend/src/queries/utils.ts @@ -3,8 +3,8 @@ import type { Query } from "@tanstack/react-query"; export const shouldRetryAllExpect403 = (failureCount: number, error: Error) => { if (error && "statusCode" in error) { if (error.statusCode === 403) { - // Don't retry on auth errors - return false; + // Don't retry on auth errors, when app is not engine + return __APP_TYPE__ !== "engine"; } } diff --git a/frontend/src/routeTree.gen.ts b/frontend/src/routeTree.gen.ts index faab076678..f8ea126f4e 100644 --- a/frontend/src/routeTree.gen.ts +++ b/frontend/src/routeTree.gen.ts @@ -21,7 +21,7 @@ import { Route as ContextEngineNsNamespaceRouteImport } from './routes/_context/ import { Route as ContextCloudOrgsOrganizationRouteImport } from './routes/_context/_cloud/orgs.$organization' import { Route as ContextEngineNsNamespaceIndexRouteImport } from './routes/_context/_engine/ns.$namespace/index' import { Route as ContextCloudOrgsOrganizationIndexRouteImport } from './routes/_context/_cloud/orgs.$organization/index' -import { Route as ContextEngineNsNamespaceRunnersRouteImport } from './routes/_context/_engine/ns.$namespace/runners' +import { Route as ContextEngineNsNamespaceConnectRouteImport } from './routes/_context/_engine/ns.$namespace/connect' import { Route as ContextCloudOrgsOrganizationProjectsIndexRouteImport } from './routes/_context/_cloud/orgs.$organization/projects.index' import { Route as ContextCloudOrgsOrganizationProjectsProjectRouteImport } from './routes/_context/_cloud/orgs.$organization/projects.$project' import { Route as ContextCloudOrgsOrganizationProjectsProjectIndexRouteImport } from './routes/_context/_cloud/orgs.$organization/projects.$project/index' @@ -90,10 +90,10 @@ const ContextCloudOrgsOrganizationIndexRoute = path: '/', getParentRoute: () => ContextCloudOrgsOrganizationRoute, } as any) -const ContextEngineNsNamespaceRunnersRoute = - ContextEngineNsNamespaceRunnersRouteImport.update({ - id: '/runners', - path: '/runners', +const ContextEngineNsNamespaceConnectRoute = + ContextEngineNsNamespaceConnectRouteImport.update({ + id: '/connect', + path: '/connect', getParentRoute: () => ContextEngineNsNamespaceRoute, } as any) const ContextCloudOrgsOrganizationProjectsIndexRoute = @@ -147,7 +147,7 @@ export interface FileRoutesByFullPath { '/orgs/$organization': typeof ContextCloudOrgsOrganizationRouteWithChildren '/ns/$namespace': typeof ContextEngineNsNamespaceRouteWithChildren '/orgs': typeof ContextCloudOrgsIndexRoute - '/ns/$namespace/runners': typeof ContextEngineNsNamespaceRunnersRoute + '/ns/$namespace/connect': typeof ContextEngineNsNamespaceConnectRoute '/orgs/$organization/': typeof ContextCloudOrgsOrganizationIndexRoute '/ns/$namespace/': typeof ContextEngineNsNamespaceIndexRoute '/orgs/$organization/projects/$project': typeof ContextCloudOrgsOrganizationProjectsProjectRouteWithChildren @@ -163,7 +163,7 @@ export interface FileRoutesByTo { '/sso-callback': typeof SsoCallbackRoute '/': typeof ContextIndexRoute '/orgs': typeof ContextCloudOrgsIndexRoute - '/ns/$namespace/runners': typeof ContextEngineNsNamespaceRunnersRoute + '/ns/$namespace/connect': typeof ContextEngineNsNamespaceConnectRoute '/orgs/$organization': typeof ContextCloudOrgsOrganizationIndexRoute '/ns/$namespace': typeof ContextEngineNsNamespaceIndexRoute '/orgs/$organization/projects': typeof ContextCloudOrgsOrganizationProjectsIndexRoute @@ -183,7 +183,7 @@ export interface FileRoutesById { '/_context/_cloud/orgs/$organization': typeof ContextCloudOrgsOrganizationRouteWithChildren '/_context/_engine/ns/$namespace': typeof ContextEngineNsNamespaceRouteWithChildren '/_context/_cloud/orgs/': typeof ContextCloudOrgsIndexRoute - '/_context/_engine/ns/$namespace/runners': typeof ContextEngineNsNamespaceRunnersRoute + '/_context/_engine/ns/$namespace/connect': typeof ContextEngineNsNamespaceConnectRoute '/_context/_cloud/orgs/$organization/': typeof ContextCloudOrgsOrganizationIndexRoute '/_context/_engine/ns/$namespace/': typeof ContextEngineNsNamespaceIndexRoute '/_context/_cloud/orgs/$organization/projects/$project': typeof ContextCloudOrgsOrganizationProjectsProjectRouteWithChildren @@ -203,7 +203,7 @@ export interface FileRouteTypes { | '/orgs/$organization' | '/ns/$namespace' | '/orgs' - | '/ns/$namespace/runners' + | '/ns/$namespace/connect' | '/orgs/$organization/' | '/ns/$namespace/' | '/orgs/$organization/projects/$project' @@ -219,7 +219,7 @@ export interface FileRouteTypes { | '/sso-callback' | '/' | '/orgs' - | '/ns/$namespace/runners' + | '/ns/$namespace/connect' | '/orgs/$organization' | '/ns/$namespace' | '/orgs/$organization/projects' @@ -238,7 +238,7 @@ export interface FileRouteTypes { | '/_context/_cloud/orgs/$organization' | '/_context/_engine/ns/$namespace' | '/_context/_cloud/orgs/' - | '/_context/_engine/ns/$namespace/runners' + | '/_context/_engine/ns/$namespace/connect' | '/_context/_cloud/orgs/$organization/' | '/_context/_engine/ns/$namespace/' | '/_context/_cloud/orgs/$organization/projects/$project' @@ -342,11 +342,11 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ContextCloudOrgsOrganizationIndexRouteImport parentRoute: typeof ContextCloudOrgsOrganizationRoute } - '/_context/_engine/ns/$namespace/runners': { - id: '/_context/_engine/ns/$namespace/runners' - path: '/runners' - fullPath: '/ns/$namespace/runners' - preLoaderRoute: typeof ContextEngineNsNamespaceRunnersRouteImport + '/_context/_engine/ns/$namespace/connect': { + id: '/_context/_engine/ns/$namespace/connect' + path: '/connect' + fullPath: '/ns/$namespace/connect' + preLoaderRoute: typeof ContextEngineNsNamespaceConnectRouteImport parentRoute: typeof ContextEngineNsNamespaceRoute } '/_context/_cloud/orgs/$organization/projects/': { @@ -467,13 +467,13 @@ const ContextCloudRouteWithChildren = ContextCloudRoute._addFileChildren( ) interface ContextEngineNsNamespaceRouteChildren { - ContextEngineNsNamespaceRunnersRoute: typeof ContextEngineNsNamespaceRunnersRoute + ContextEngineNsNamespaceConnectRoute: typeof ContextEngineNsNamespaceConnectRoute ContextEngineNsNamespaceIndexRoute: typeof ContextEngineNsNamespaceIndexRoute } const ContextEngineNsNamespaceRouteChildren: ContextEngineNsNamespaceRouteChildren = { - ContextEngineNsNamespaceRunnersRoute: ContextEngineNsNamespaceRunnersRoute, + ContextEngineNsNamespaceConnectRoute: ContextEngineNsNamespaceConnectRoute, ContextEngineNsNamespaceIndexRoute: ContextEngineNsNamespaceIndexRoute, } diff --git a/frontend/src/routes/_context/_cloud.tsx b/frontend/src/routes/_context/_cloud.tsx index 3b8e38c3b5..95e6bb173b 100644 --- a/frontend/src/routes/_context/_cloud.tsx +++ b/frontend/src/routes/_context/_cloud.tsx @@ -77,6 +77,9 @@ function CloudModals() { }} /> Object.values(data.pages[0].runnerConfigs).length, @@ -58,16 +55,14 @@ function RouteComponent() {
    {isLoading ? ( - <> -
    - -
    - - - -
    +
    + +
    + + +
    - +
    ) : configsCount === 0 ? (

    Select Provider

    @@ -147,10 +142,6 @@ function Providers() { } function Runners() { - const params = Route.useParams(); - const { data: namespace } = useQuery( - useCloudDataProvider().currentOrgProjectNamespaceQueryOptions(params), - ); const { isLoading, isError, @@ -158,11 +149,8 @@ function Runners() { hasNextPage, fetchNextPage, } = useInfiniteQuery({ - ...useEngineCompatDataProvider().runnersQueryOptions({ - namespace: namespace?.access.engineNamespaceName || "", - }), + ...useEngineCompatDataProvider().runnersQueryOptions(), refetchInterval: 5000, - enabled: !!namespace, }); return ( diff --git a/frontend/src/routes/_context/_engine.tsx b/frontend/src/routes/_context/_engine.tsx index 5bdf34c7b5..dfc8cdcd8a 100644 --- a/frontend/src/routes/_context/_engine.tsx +++ b/frontend/src/routes/_context/_engine.tsx @@ -34,23 +34,68 @@ function EngineModals() { const CreateNamespaceDialog = useDialog.CreateNamespace.Dialog; + const ConnectVercelDialog = useDialog.ConnectVercel.Dialog; + const ConnectRailwayDialog = useDialog.ConnectRailway.Dialog; + return ( - { - if (!value) { - navigate({ - to: ".", - search: (old) => ({ - ...old, - modal: undefined, - }), - }); - } - }, - }} - /> + <> + { + if (!value) { + navigate({ + to: ".", + search: (old) => ({ + ...old, + modal: undefined, + }), + }); + } + }, + }} + /> + { + if (!value) { + navigate({ + to: ".", + search: (old) => ({ + ...old, + modal: undefined, + }), + }); + } + }, + }} + /> + { + if (!value) { + navigate({ + to: ".", + search: (old) => ({ + ...old, + modal: undefined, + }), + }); + } + }, + }} + /> + ); } diff --git a/frontend/src/routes/_context/_engine/ns.$namespace/connect.tsx b/frontend/src/routes/_context/_engine/ns.$namespace/connect.tsx new file mode 100644 index 0000000000..e3623d0a04 --- /dev/null +++ b/frontend/src/routes/_context/_engine/ns.$namespace/connect.tsx @@ -0,0 +1,8 @@ +import { createFileRoute } from "@tanstack/react-router"; +import { RouteComponent } from "../../_cloud/orgs.$organization/projects.$project/ns.$namespace/connect"; + +export const Route = createFileRoute("/_context/_engine/ns/$namespace/connect")( + { + component: RouteComponent, + }, +); diff --git a/frontend/src/routes/_context/_engine/ns.$namespace/runners.tsx b/frontend/src/routes/_context/_engine/ns.$namespace/runners.tsx deleted file mode 100644 index 54f0ae3cde..0000000000 --- a/frontend/src/routes/_context/_engine/ns.$namespace/runners.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import { faRefresh, Icon } from "@rivet-gg/icons"; -import { useInfiniteQuery } from "@tanstack/react-query"; -import { createFileRoute } from "@tanstack/react-router"; -import { RunnersTable } from "@/app/runners-table"; -import { - Button, - H1, - WithTooltip, -} from "@/components"; -import { useEngineCompatDataProvider } from "@/components/actors"; - -export const Route = createFileRoute("/_context/_engine/ns/$namespace/runners")( - { - component: RouteComponent, - }, -); - -function RouteComponent() { - const { namespace } = Route.useParams(); - const { - data: runners, - isRefetching, - hasNextPage, - fetchNextPage, - isLoading, - isError, - refetch, - } = useInfiniteQuery( - useEngineCompatDataProvider().runnersQueryOptions({ - namespace: namespace, - }), - ); - - return ( -
    -
    -

    Runners

    -
    - refetch()} - > - - - } - /> -
    -
    - -
    - -
    -
    -
    - -
    -
    -
    -
    - ); -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3c483fc0ae..8ea28d1d66 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -177,8 +177,8 @@ importers: specifier: ^1.2.3 version: 1.2.3(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@rivet-gg/cloud': - specifier: file:vendor/rivet-cloud.tgz - version: file:frontend/vendor/rivet-cloud.tgz + specifier: https://pkg.pr.new/rivet-dev/cloud/@rivet-gg/cloud@7090780 + version: https://pkg.pr.new/rivet-dev/cloud/@rivet-gg/cloud@7090780 '@rivet-gg/icons': specifier: workspace:* version: link:packages/icons @@ -197,6 +197,9 @@ importers: '@shikijs/transformers': specifier: ^3.12.2 version: 3.12.2 + '@stepperize/react': + specifier: ^5.1.8 + version: 5.1.8(react@19.1.1)(typescript@5.9.2) '@tailwindcss/container-queries': specifier: ^0.1.1 version: 0.1.1(tailwindcss@3.4.17) @@ -851,7 +854,7 @@ importers: version: 15.5.2(@mdx-js/loader@3.1.1(webpack@5.101.3(esbuild@0.25.9)))(@mdx-js/react@3.1.1(@types/react@18.3.24)(react@19.1.1)) '@next/third-parties': specifier: latest - version: 15.5.3(next@15.5.2(babel-plugin-macros@3.1.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.93.2))(react@19.1.1) + version: 15.5.4(next@15.5.2(babel-plugin-macros@3.1.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.93.2))(react@19.1.1) '@rivet-gg/api': specifier: 0.0.1-rc8 version: 0.0.1-rc8 @@ -2454,8 +2457,8 @@ packages: cpu: [x64] os: [win32] - '@next/third-parties@15.5.3': - resolution: {integrity: sha512-DSKz1N6/WmacV8JAPizHt8dqTP6RljcOgtrnLDEIEI7TGpX1lSfoHwY0TxXVhVoN1WwUHGDo6XUJsSjaLpcMoQ==} + '@next/third-parties@15.5.4': + resolution: {integrity: sha512-l3T1M/EA32phPzZx+gkQAWOF3E5iAULL1nX4Ej0JZQOXaBwwJzb/rd2uefr5TAshJj/+HjjwmdFu7olXudvgVg==} peerDependencies: next: ^13.0.0 || ^14.0.0 || ^15.0.0 react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 @@ -3181,8 +3184,8 @@ packages: '@rivet-gg/api@0.0.1-rc8': resolution: {integrity: sha512-aGJVImxsmz8fLLzeZHUlFRJ/7Y/xBrke9bOvMpooVaJpHor/XmiP19QeEtB2hmQUOPlgS3dz5o8UtCZ5+LcQGg==} - '@rivet-gg/cloud@file:frontend/vendor/rivet-cloud.tgz': - resolution: {integrity: sha512-80sLz4sqI1x+nsaPwIgk+OnpFEx+ybJp17QsAz+WO0JQBzbECxo4dUZaCoTNorA+8T6cM8qfN2/fIeqpsAHs7A==, tarball: file:frontend/vendor/rivet-cloud.tgz} + '@rivet-gg/cloud@https://pkg.pr.new/rivet-dev/cloud/@rivet-gg/cloud@7090780': + resolution: {tarball: https://pkg.pr.new/rivet-dev/cloud/@rivet-gg/cloud@7090780} version: 0.0.0 '@rivetkit/engine-runner-protocol@https://pkg.pr.new/rivet-dev/engine/@rivetkit/engine-runner-protocol@b72b2324c50c5449ed1844a060928d80d1151839': @@ -3504,6 +3507,16 @@ packages: '@standard-schema/spec@1.0.0': resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} + '@stepperize/core@1.2.7': + resolution: {integrity: sha512-XiUwLZ0XRAfaDK6AzWVgqvI/BcrylyplhUXKO8vzgRw0FTmyMKHAAbQLDvU//ZJAqnmG2cSLZDSkcwLxU5zSYA==} + peerDependencies: + typescript: '>=5.0.2' + + '@stepperize/react@5.1.8': + resolution: {integrity: sha512-/s8+YoVjX2+kPRxEMrmJZLX9jnVa/tKS+7Ru6ZUvBNSmbIopf0deylMv8hE2E5Il4T/UI2aSX/d3mKu8gugomw==} + peerDependencies: + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + '@stripe/stripe-js@5.6.0': resolution: {integrity: sha512-w8CEY73X/7tw2KKlL3iOk679V9bWseE4GzNz3zlaYxcTjmcmWOathRb0emgo/QQ3eoNzmq68+2Y2gxluAv3xGw==} engines: {node: '>=12.16'} @@ -4499,6 +4512,9 @@ packages: resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} engines: {node: '>=12.5.0'} + colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + combined-stream@1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} @@ -4661,6 +4677,9 @@ packages: date-fns@4.1.0: resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} + dateformat@4.6.3: + resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==} + de-indent@1.0.2: resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==} @@ -5093,6 +5112,9 @@ packages: resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} engines: {node: '>=4'} + fast-copy@3.0.2: + resolution: {integrity: sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -5120,6 +5142,9 @@ packages: resolution: {integrity: sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==} engines: {node: '>=6'} + fast-safe-stringify@2.1.1: + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + fast-uri@3.1.0: resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} @@ -5443,6 +5468,9 @@ packages: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true + help-me@5.0.0: + resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==} + highlight-words-core@1.2.3: resolution: {integrity: sha512-m1O9HW3/GNHxzSIXWw1wCNXXsgLlxrP0OI6+ycGUhiUHkikqW3OrwVHz+lxeNBe5yqLESdIcj8PowHQ2zLvUvQ==} @@ -6595,6 +6623,10 @@ packages: pino-abstract-transport@2.0.0: resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==} + pino-pretty@13.1.1: + resolution: {integrity: sha512-TNNEOg0eA0u+/WuqH0MH0Xui7uqVk9D74ESOpjtebSQYbNWJk/dIxCXIxFsNfeN53JmtWqYHP2OrIZjT/CBEnA==} + hasBin: true + pino-std-serializers@7.0.0: resolution: {integrity: sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==} @@ -7223,6 +7255,9 @@ packages: resolution: {integrity: sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==} engines: {node: '>= 10.13.0'} + secure-json-parse@4.0.0: + resolution: {integrity: sha512-dxtLJO6sc35jWidmLxo7ij+Eg48PM/kleBsxpC8QJE0qJICe+KawkDQmvCMZUr9u7WKVHgMW6vy3fQ7zMiFZMA==} + semver@5.7.2: resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} hasBin: true @@ -7473,6 +7508,10 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + strip-json-comments@5.0.3: + resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==} + engines: {node: '>=14.16'} + strip-literal@2.1.1: resolution: {integrity: sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==} @@ -9685,7 +9724,7 @@ snapshots: '@next/swc-win32-x64-msvc@15.5.2': optional: true - '@next/third-parties@15.5.3(next@15.5.2(babel-plugin-macros@3.1.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.93.2))(react@19.1.1)': + '@next/third-parties@15.5.4(next@15.5.2(babel-plugin-macros@3.1.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.93.2))(react@19.1.1)': dependencies: next: 15.5.2(babel-plugin-macros@3.1.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.93.2) react: 19.1.1 @@ -10431,12 +10470,13 @@ snapshots: transitivePeerDependencies: - debug - '@rivet-gg/cloud@file:frontend/vendor/rivet-cloud.tgz': + '@rivet-gg/cloud@https://pkg.pr.new/rivet-dev/cloud/@rivet-gg/cloud@7090780': dependencies: cross-fetch: 4.1.0 form-data: 4.0.4 js-base64: 3.7.8 node-fetch: 2.7.0 + pino-pretty: 13.1.1 qs: 6.14.0 readable-stream: 4.7.0 url-join: 5.0.0 @@ -10836,6 +10876,17 @@ snapshots: '@standard-schema/spec@1.0.0': {} + '@stepperize/core@1.2.7(typescript@5.9.2)': + dependencies: + typescript: 5.9.2 + + '@stepperize/react@5.1.8(react@19.1.1)(typescript@5.9.2)': + dependencies: + '@stepperize/core': 1.2.7(typescript@5.9.2) + react: 19.1.1 + transitivePeerDependencies: + - typescript + '@stripe/stripe-js@5.6.0': {} '@swc/helpers@0.5.15': @@ -11969,6 +12020,8 @@ snapshots: color-convert: 2.0.1 color-string: 1.9.1 + colorette@2.0.20: {} + combined-stream@1.0.8: dependencies: delayed-stream: 1.0.0 @@ -12111,6 +12164,8 @@ snapshots: date-fns@4.1.0: {} + dateformat@4.6.3: {} + de-indent@1.0.2: {} debug@3.2.7: @@ -12485,7 +12540,7 @@ snapshots: eslint: 8.26.0 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 2.7.1(eslint-plugin-import@2.32.0(eslint@8.26.0))(eslint@8.26.0) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@5.62.0(eslint@8.26.0)(typescript@5.9.2))(eslint-import-resolver-typescript@2.7.1(eslint-plugin-import@2.32.0(eslint@8.26.0))(eslint@8.26.0))(eslint@8.26.0) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@5.62.0(eslint@8.26.0)(typescript@5.9.2))(eslint-import-resolver-typescript@2.7.1)(eslint@8.26.0) eslint-plugin-jsx-a11y: 6.10.2(eslint@8.26.0) eslint-plugin-react: 7.37.5(eslint@8.26.0) eslint-plugin-react-hooks: 4.6.2(eslint@8.26.0) @@ -12507,7 +12562,7 @@ snapshots: dependencies: debug: 4.4.1 eslint: 8.26.0 - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@5.62.0(eslint@8.26.0)(typescript@5.9.2))(eslint-import-resolver-typescript@2.7.1(eslint-plugin-import@2.32.0(eslint@8.26.0))(eslint@8.26.0))(eslint@8.26.0) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@5.62.0(eslint@8.26.0)(typescript@5.9.2))(eslint-import-resolver-typescript@2.7.1)(eslint@8.26.0) glob: 7.2.3 is-glob: 4.0.3 resolve: 1.22.10 @@ -12526,7 +12581,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@5.62.0(eslint@8.26.0)(typescript@5.9.2))(eslint-import-resolver-typescript@2.7.1(eslint-plugin-import@2.32.0(eslint@8.26.0))(eslint@8.26.0))(eslint@8.26.0): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@5.62.0(eslint@8.26.0)(typescript@5.9.2))(eslint-import-resolver-typescript@2.7.1)(eslint@8.26.0): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -12757,6 +12812,8 @@ snapshots: iconv-lite: 0.4.24 tmp: 0.0.33 + fast-copy@3.0.2: {} + fast-deep-equal@3.1.3: {} fast-equals@5.2.2: {} @@ -12779,6 +12836,8 @@ snapshots: fast-redact@3.5.0: {} + fast-safe-stringify@2.1.1: {} + fast-uri@3.1.0: {} fastest-levenshtein@1.0.16: {} @@ -13188,6 +13247,8 @@ snapshots: he@1.2.0: {} + help-me@5.0.0: {} + highlight-words-core@1.2.3: {} hoist-non-react-statics@3.3.2: @@ -14578,6 +14639,22 @@ snapshots: dependencies: split2: 4.2.0 + pino-pretty@13.1.1: + dependencies: + colorette: 2.0.20 + dateformat: 4.6.3 + fast-copy: 3.0.2 + fast-safe-stringify: 2.1.1 + help-me: 5.0.0 + joycon: 3.1.1 + minimist: 1.2.8 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 2.0.0 + pump: 3.0.3 + secure-json-parse: 4.0.0 + sonic-boom: 4.2.0 + strip-json-comments: 5.0.3 + pino-std-serializers@7.0.0: {} pino@9.9.5: @@ -15280,6 +15357,8 @@ snapshots: ajv-formats: 2.1.1(ajv@8.17.1) ajv-keywords: 5.1.0(ajv@8.17.1) + secure-json-parse@4.0.0: {} + semver@5.7.2: optional: true @@ -15657,6 +15736,8 @@ snapshots: strip-json-comments@3.1.1: {} + strip-json-comments@5.0.3: {} + strip-literal@2.1.1: dependencies: js-tokens: 9.0.1