From 9be94b3985d4a84ed87a7a104c06878d1b88d59e Mon Sep 17 00:00:00 2001 From: Emma Hamilton Date: Wed, 8 May 2024 12:35:27 +1000 Subject: [PATCH] Replace zod with superstruct --- packages/keystatic/package.json | 4 +- packages/keystatic/src/api/api-node.ts | 67 ++++++++++--------- packages/keystatic/src/api/generic.ts | 34 +++++----- packages/keystatic/src/app/ItemPage.tsx | 18 ++--- packages/keystatic/src/app/SingletonPage.tsx | 16 ++--- packages/keystatic/src/app/auth.ts | 12 ++-- .../keystatic/src/app/cloud-auth-callback.tsx | 44 ++++++------ packages/keystatic/src/app/create-item.tsx | 16 ++--- packages/keystatic/src/app/object-cache.ts | 36 ++++++---- packages/keystatic/src/app/shell/data.tsx | 32 ++++----- .../component-blocks/cloud-image-preview.tsx | 36 +++++----- pnpm-lock.yaml | 15 +++-- 12 files changed, 175 insertions(+), 155 deletions(-) diff --git a/packages/keystatic/package.json b/packages/keystatic/package.json index bd6203ad8..b5a12a11f 100644 --- a/packages/keystatic/package.json +++ b/packages/keystatic/package.json @@ -180,12 +180,12 @@ "slate": "^0.91.4", "slate-history": "^0.86.0", "slate-react": "^0.91.9", + "superstruct": "^1.0.4", "unist-util-visit": "^5.0.0", "urql": "^4.0.0", "y-prosemirror": "^1.2.2", "y-protocols": "^1.0.6", - "yjs": "^13.6.11", - "zod": "^3.20.2" + "yjs": "^13.6.11" }, "devDependencies": { "@jest/expect": "^29.7.0", diff --git a/packages/keystatic/src/api/api-node.ts b/packages/keystatic/src/api/api-node.ts index bd1059aad..b36b9e95b 100644 --- a/packages/keystatic/src/api/api-node.ts +++ b/packages/keystatic/src/api/api-node.ts @@ -1,5 +1,5 @@ import path from 'node:path'; -import { z } from 'zod'; +import * as s from 'superstruct'; import fs from 'node:fs/promises'; import { Config } from '../config'; import { @@ -10,6 +10,7 @@ import { import { readToDirEntries, getAllowedDirectories } from './read-local'; import { blobSha } from '../app/trees'; import { randomBytes } from 'node:crypto'; +import { base64UrlDecode } from '#base64'; // this should be trivially dead code eliminated // it's just to ensure the types are exactly the same between this and local-noop.ts @@ -23,10 +24,10 @@ function _typeTest() { let _d: typeof b = a; } -const ghAppSchema = z.object({ - slug: z.string(), - client_id: z.string(), - client_secret: z.string(), +const ghAppSchema = s.type({ + slug: s.string(), + client_id: s.string(), + client_secret: s.string(), }); const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); @@ -55,10 +56,10 @@ export async function handleGitHubAppCreation( }; } const ghAppDataRaw = await ghAppRes.json(); - - const ghAppDataResult = ghAppSchema.safeParse(ghAppDataRaw); - - if (!ghAppDataResult.success) { + let ghAppDataResult; + try { + ghAppDataResult = s.create(ghAppDataRaw, ghAppSchema); + } catch { console.log(ghAppDataRaw); return { status: 500, @@ -66,10 +67,10 @@ export async function handleGitHubAppCreation( }; } const toAddToEnv = `# Keystatic -KEYSTATIC_GITHUB_CLIENT_ID=${ghAppDataResult.data.client_id} -KEYSTATIC_GITHUB_CLIENT_SECRET=${ghAppDataResult.data.client_secret} +KEYSTATIC_GITHUB_CLIENT_ID=${ghAppDataResult.client_id} +KEYSTATIC_GITHUB_CLIENT_SECRET=${ghAppDataResult.client_secret} KEYSTATIC_SECRET=${randomBytes(40).toString('hex')} -${slugEnvVarName ? `${slugEnvVarName}=${ghAppDataResult.data.slug}\n` : ''}`; +${slugEnvVarName ? `${slugEnvVarName}=${ghAppDataResult.slug}\n` : ''}`; let prevEnv: string | undefined; try { @@ -80,9 +81,7 @@ ${slugEnvVarName ? `${slugEnvVarName}=${ghAppDataResult.data.slug}\n` : ''}`; const newEnv = prevEnv ? `${prevEnv}\n\n${toAddToEnv}` : toAddToEnv; await fs.writeFile('.env', newEnv); await wait(200); - return redirect( - '/keystatic/created-github-app?slug=' + ghAppDataResult.data.slug - ); + return redirect('/keystatic/created-github-app?slug=' + ghAppDataResult.slug); } export function localModeApiHandler( @@ -165,6 +164,10 @@ async function blob( return { status: 200, body: contents }; } +const base64Schema = s.coerce(s.instance(Uint8Array), s.string(), val => + base64UrlDecode(val) +); + async function update( req: KeystaticRequest, config: Config, @@ -177,24 +180,26 @@ async function update( return { status: 400, body: 'Bad Request' }; } const isFilepathValid = getIsPathValid(config); + const filepath = s.refine(s.string(), 'filepath', isFilepathValid); + let updates; - const updates = z - .object({ - additions: z.array( - z.object({ - path: z.string().refine(isFilepathValid), - contents: z.string().transform(x => Buffer.from(x, 'base64')), - }) - ), - deletions: z.array( - z.object({ path: z.string().refine(isFilepathValid) }) - ), - }) - .safeParse(await req.json()); - if (!updates.success) { + try { + updates = s.create( + await req.json(), + s.object({ + additions: s.array( + s.object({ + path: filepath, + contents: base64Schema, + }) + ), + deletions: s.array(s.object({ path: filepath })), + }) + ); + } catch { return { status: 400, body: 'Bad data' }; } - for (const addition of updates.data.additions) { + for (const addition of updates.additions) { await fs.mkdir(path.dirname(path.join(baseDirectory, addition.path)), { recursive: true, }); @@ -203,7 +208,7 @@ async function update( addition.contents ); } - for (const deletion of updates.data.deletions) { + for (const deletion of updates.deletions) { await fs.rm(path.join(baseDirectory, deletion.path), { force: true }); } return { diff --git a/packages/keystatic/src/api/generic.ts b/packages/keystatic/src/api/generic.ts index 7015d3b95..897a1b141 100644 --- a/packages/keystatic/src/api/generic.ts +++ b/packages/keystatic/src/api/generic.ts @@ -1,5 +1,5 @@ import cookie from 'cookie'; -import z from 'zod'; +import * as s from 'superstruct'; import { Config } from '..'; import { KeystaticResponse, @@ -181,13 +181,13 @@ export function makeGenericAPIRouteHandler( }; } -const tokenDataResultType = z.object({ - access_token: z.string(), - expires_in: z.number(), - refresh_token: z.string(), - refresh_token_expires_in: z.number(), - scope: z.string(), - token_type: z.literal('bearer'), +const tokenDataResultType = s.type({ + access_token: s.string(), + expires_in: s.number(), + refresh_token: s.string(), + refresh_token_expires_in: s.number(), + scope: s.string(), + token_type: s.literal('bearer'), }); async function githubOauthCallback( @@ -231,12 +231,14 @@ async function githubOauthCallback( return { status: 401, body: 'Authorization failed' }; } const _tokenData = await tokenRes.json(); - const tokenDataParseResult = tokenDataResultType.safeParse(_tokenData); - if (!tokenDataParseResult.success) { + let tokenData; + try { + tokenData = tokenDataResultType.create(_tokenData); + } catch { return { status: 401, body: 'Authorization failed' }; } - const headers = await getTokenCookies(tokenDataParseResult.data, config); + const headers = await getTokenCookies(tokenData, config); if (state === 'close') { return { headers: [...headers, ['Content-Type', 'text/html']], @@ -248,7 +250,7 @@ async function githubOauthCallback( } async function getTokenCookies( - tokenData: z.infer, + tokenData: s.Infer, config: InnerAPIRouteConfig ) { const headers: [string, string][] = [ @@ -332,11 +334,13 @@ async function refreshGitHubAuth( return; } const _tokenData = await tokenRes.json(); - const tokenDataParseResult = tokenDataResultType.safeParse(_tokenData); - if (!tokenDataParseResult.success) { + let tokenData; + try { + tokenData = tokenDataResultType.create(_tokenData); + } catch { return; } - return getTokenCookies(tokenDataParseResult.data, config); + return getTokenCookies(tokenData, config); } async function githubRepoNotFound( diff --git a/packages/keystatic/src/app/ItemPage.tsx b/packages/keystatic/src/app/ItemPage.tsx index 5f65292f5..20b4215d3 100644 --- a/packages/keystatic/src/app/ItemPage.tsx +++ b/packages/keystatic/src/app/ItemPage.tsx @@ -11,7 +11,7 @@ import { useState, } from 'react'; import * as Y from 'yjs'; -import { z } from 'zod'; +import * as s from 'superstruct'; import { ActionGroup, Item } from '@keystar/ui/action-group'; import { Badge } from '@keystar/ui/badge'; @@ -95,12 +95,12 @@ type ItemPageProps = { basePath: string; }; -const storedValSchema = z.object({ - version: z.literal(1), - savedAt: z.date(), - slug: z.string(), - beforeTreeKey: z.string(), - files: z.map(z.string(), z.instanceof(Uint8Array)), +const storedValSchema = s.type({ + version: s.literal(1), + savedAt: s.date(), + slug: s.string(), + beforeTreeKey: s.string(), + files: s.map(s.string(), s.instance(Uint8Array)), }); function ItemPageInner( @@ -433,7 +433,7 @@ function LocalItemPage( state, }); const files = new Map(serialized.map(x => [x.path, x.contents])); - const data: z.infer = { + const data: s.Infer = { beforeTreeKey: localTreeKey, slug, files, @@ -837,7 +837,7 @@ function ItemPageWrapper(props: ItemPageWrapperProps) { props.itemSlug, ]); if (!raw) throw new Error('No draft found'); - const stored = storedValSchema.parse(raw); + const stored = storedValSchema.create(raw); const parsed = parseEntry( { config: props.config, diff --git a/packages/keystatic/src/app/SingletonPage.tsx b/packages/keystatic/src/app/SingletonPage.tsx index 5923d4f41..2afb76c75 100644 --- a/packages/keystatic/src/app/SingletonPage.tsx +++ b/packages/keystatic/src/app/SingletonPage.tsx @@ -48,7 +48,7 @@ import { setDraft, showDraftRestoredToast, } from './persistence'; -import { z } from 'zod'; +import * as s from 'superstruct'; import { LOADING, useData } from './useData'; import { ActionGroup, Item } from '@keystar/ui/action-group'; import { useMediaQuery, breakpointQueries } from '@keystar/ui/style'; @@ -356,7 +356,7 @@ function LocalSingletonPage( state, }); const files = new Map(serialized.map(x => [x.path, x.contents])); - const data: z.infer = { + const data: s.Infer = { beforeTreeKey: localTreeKey, files, savedAt: new Date(), @@ -488,11 +488,11 @@ function CollabSingletonPage( ); } -const storedValSchema = z.object({ - version: z.literal(1), - savedAt: z.date(), - beforeTreeKey: z.string().optional(), - files: z.map(z.string(), z.instanceof(Uint8Array)), +const storedValSchema = s.type({ + version: s.literal(1), + savedAt: s.date(), + beforeTreeKey: s.optional(s.string()), + files: s.map(s.string(), s.instance(Uint8Array)), }); function SingletonPageWrapper(props: { singleton: string; config: Config }) { @@ -516,7 +516,7 @@ function SingletonPageWrapper(props: { singleton: string; config: Config }) { useCallback(async () => { const raw = await getDraft(['singleton', props.singleton]); if (!raw) throw new Error('No draft found'); - const stored = storedValSchema.parse(raw); + const stored = storedValSchema.create(raw); const parsed = parseEntry( { config: props.config, diff --git a/packages/keystatic/src/app/auth.ts b/packages/keystatic/src/app/auth.ts index 8420d5b4c..7eee7e98a 100644 --- a/packages/keystatic/src/app/auth.ts +++ b/packages/keystatic/src/app/auth.ts @@ -1,11 +1,11 @@ import { parse } from 'cookie'; -import { z } from 'zod'; +import * as s from 'superstruct'; import { Config } from '../config'; -const storedTokenSchema = z.object({ - token: z.string(), - project: z.string(), - validUntil: z.number().transform(val => new Date(val)), +const storedTokenSchema = s.object({ + token: s.string(), + project: s.string(), + validUntil: s.coerce(s.date(), s.number(), val => new Date(val)), }); export function getSyncAuth(config: Config) { @@ -33,7 +33,7 @@ export function getCloudAuth(config: Config) { ); let tokenData; try { - tokenData = storedTokenSchema.parse(JSON.parse(unparsedTokenData!)); + tokenData = storedTokenSchema.create(JSON.parse(unparsedTokenData!)); } catch (err) { return null; } diff --git a/packages/keystatic/src/app/cloud-auth-callback.tsx b/packages/keystatic/src/app/cloud-auth-callback.tsx index e76eab8d4..cec4eae10 100644 --- a/packages/keystatic/src/app/cloud-auth-callback.tsx +++ b/packages/keystatic/src/app/cloud-auth-callback.tsx @@ -1,21 +1,21 @@ import { ProgressCircle } from '@keystar/ui/progress'; import { Text } from '@keystar/ui/typography'; import { useEffect, useMemo, useState } from 'react'; -import { z } from 'zod'; +import * as s from 'superstruct'; import { Config } from '../config'; import { useRouter } from './router'; import { KEYSTATIC_CLOUD_API_URL, KEYSTATIC_CLOUD_HEADERS } from './utils'; import { Flex } from '@keystar/ui/layout'; -const storedStateSchema = z.object({ - state: z.string(), - from: z.string(), - code_verifier: z.string(), +const storedStateSchema = s.object({ + state: s.string(), + from: s.string(), + code_verifier: s.string(), }); -const tokenResponseSchema = z.object({ - access_token: z.string(), - token_type: z.string(), - expires_in: z.number(), +const tokenResponseSchema = s.type({ + access_token: s.string(), + token_type: s.string(), + expires_in: s.number(), }); export function KeystaticCloudAuthCallback({ config }: { config: Config }) { @@ -25,20 +25,18 @@ export function KeystaticCloudAuthCallback({ config }: { config: Config }) { const state = url.searchParams.get('state'); const storedState = useMemo(() => { const _storedState = localStorage.getItem('keystatic-cloud-state'); - const storedState = storedStateSchema.safeParse( - (() => { - try { - return JSON.parse(_storedState || ''); - } catch { - return null; - } - })() - ); + const storedState = (() => { + try { + return storedStateSchema.create(JSON.parse(_storedState || '')); + } catch { + return null; + } + })(); return storedState; }, []); const [error, setError] = useState(null); useEffect(() => { - if (code && state && storedState.success && config.cloud?.project) { + if (code && state && storedState && config.cloud?.project) { const { project } = config.cloud; (async () => { const res = await fetch(`${KEYSTATIC_CLOUD_API_URL}/oauth/token`, { @@ -47,7 +45,7 @@ export function KeystaticCloudAuthCallback({ config }: { config: Config }) { code, client_id: project, redirect_uri: `${window.location.origin}/keystatic/cloud/oauth/callback`, - code_verifier: storedState.data.code_verifier, + code_verifier: storedState.code_verifier, grant_type: 'authorization_code', }).toString(), headers: { @@ -63,7 +61,7 @@ export function KeystaticCloudAuthCallback({ config }: { config: Config }) { ); } const data = await res.json(); - const parsed = tokenResponseSchema.parse(data); + const parsed = tokenResponseSchema.create(data); localStorage.setItem( 'keystatic-cloud-access-token', JSON.stringify({ @@ -72,7 +70,7 @@ export function KeystaticCloudAuthCallback({ config }: { config: Config }) { validUntil: Date.now() + parsed.expires_in * 1000, }) ); - router.push(`/keystatic/${storedState.data.from}`); + router.push(`/keystatic/${storedState.from}`); })().catch(error => { setError(error); }); @@ -84,7 +82,7 @@ export function KeystaticCloudAuthCallback({ config }: { config: Config }) { if (!code || !state) { return Missing code or state; } - if (storedState.success === false || state !== storedState.data.state) { + if (!storedState || state !== storedState.state) { return Invalid state; } diff --git a/packages/keystatic/src/app/create-item.tsx b/packages/keystatic/src/app/create-item.tsx index d9fb5e2c3..98d557e54 100644 --- a/packages/keystatic/src/app/create-item.tsx +++ b/packages/keystatic/src/app/create-item.tsx @@ -1,7 +1,7 @@ import { useLocalizedStringFormatter } from '@react-aria/i18n'; import { useCallback, useEffect, useMemo, useState } from 'react'; import * as Y from 'yjs'; -import { z } from 'zod'; +import * as s from 'superstruct'; import { Button } from '@keystar/ui/button'; import { DialogContainer } from '@keystar/ui/dialog'; @@ -79,7 +79,7 @@ function CreateItemWrapper(props: { ...(duplicateSlug ? ([duplicateSlug] as const) : ([] as const)), ]); if (!raw) throw new Error('No draft found'); - const stored = storedValSchema.parse(raw); + const stored = storedValSchema.create(raw); const parsed = parseEntry( { config: props.config, @@ -271,11 +271,11 @@ function CreateItemWrapper(props: { ); } -const storedValSchema = z.object({ - version: z.literal(1), - savedAt: z.date(), - slug: z.string(), - files: z.map(z.string(), z.instanceof(Uint8Array)), +const storedValSchema = s.type({ + version: s.literal(1), + savedAt: s.date(), + slug: s.string(), + files: s.map(s.string(), s.instance(Uint8Array)), }); function CreateItemLocal(props: { @@ -353,7 +353,7 @@ function CreateItemLocal(props: { state, }); const files = new Map(serialized.map(x => [x.path, x.contents])); - const data: z.infer = { + const data: s.Infer = { slug, files, savedAt: new Date(), diff --git a/packages/keystatic/src/app/object-cache.ts b/packages/keystatic/src/app/object-cache.ts index a9aa5f8f5..a15576462 100644 --- a/packages/keystatic/src/app/object-cache.ts +++ b/packages/keystatic/src/app/object-cache.ts @@ -10,7 +10,7 @@ import { clear, } from 'idb-keyval'; import { TreeNode } from './trees'; -import { z } from 'zod'; +import * as s from 'superstruct'; type StoredTreeEntry = { path: string; @@ -48,11 +48,11 @@ export async function getBlobFromPersistedCache(sha: string) { } let _storedTreeCache: Map | undefined; -const treeSchema = z.array( - z.object({ - path: z.string(), - mode: z.string(), - sha: z.string(), +const treeSchema = s.array( + s.object({ + path: s.string(), + mode: s.string(), + sha: s.string(), }) ); @@ -63,10 +63,14 @@ function getStoredTrees() { const cache = new Map(); return entries(getTreeStore()).then(entries => { for (const [sha, tree] of entries) { - const parsed = treeSchema.safeParse(tree); - if (parsed.success && typeof sha === 'string') { - cache.set(sha, parsed.data); + if (typeof sha !== 'string') continue; + let parsed; + try { + parsed = treeSchema.create(tree); + } catch { + continue; } + cache.set(sha, parsed); } _storedTreeCache = cache; return cache; @@ -125,12 +129,18 @@ export async function garbageCollectGitObjects(roots: string[]) { const treesToDelete = new Map(); const invalidTrees: IDBValidKey[] = []; for (const [sha, tree] of await getStoredTrees()) { - const parsed = treeSchema.safeParse(tree); - if (parsed.success && typeof sha === 'string') { - treesToDelete.set(sha, parsed.data); - } else { + if (typeof sha !== 'string') { invalidTrees.push(sha); + continue; + } + let parsed; + try { + parsed = treeSchema.create(tree); + } catch { + invalidTrees.push(sha); + continue; } + treesToDelete.set(sha, parsed); } const allBlobs = (await keys(getBlobStore())) as string[]; diff --git a/packages/keystatic/src/app/shell/data.tsx b/packages/keystatic/src/app/shell/data.tsx index e8f1c16fb..2bed4f8cc 100644 --- a/packages/keystatic/src/app/shell/data.tsx +++ b/packages/keystatic/src/app/shell/data.tsx @@ -41,7 +41,7 @@ import { isDefined } from 'emery'; import { getAuth, getCloudAuth } from '../auth'; import { ViewerContext, SidebarFooter_viewer } from './viewer-data'; import { parseRepoConfig, serializeRepoConfig } from '../repo-config'; -import { z } from 'zod'; +import * as s from 'superstruct'; import { scopeEntriesWithPathPrefix } from './path-prefix'; import { garbageCollectGitObjects, @@ -119,26 +119,26 @@ export function LocalAppShellProvider(props: { ); } -const cloudInfoSchema = z.object({ - user: z.object({ - id: z.string(), - name: z.string(), - email: z.string(), - avatarUrl: z.string().optional(), +const cloudInfoSchema = s.type({ + user: s.type({ + id: s.string(), + name: s.string(), + email: s.string(), + avatarUrl: s.optional(s.string()), }), - project: z.object({ - name: z.string(), + project: s.type({ + name: s.string(), }), - team: z.object({ - name: z.string(), - slug: z.string(), - images: z.boolean(), - multiplayer: z.boolean(), + team: s.object({ + name: s.string(), + slug: s.string(), + images: s.boolean(), + multiplayer: s.boolean(), }), }); const CloudInfo = createContext< - null | z.infer | 'unauthorized' + null | s.Infer | 'unauthorized' >(null); export function useCloudInfo() { @@ -168,7 +168,7 @@ export function CloudInfoProvider(props: { }, }); if (res.status === 401) return 'unauthorized' as const; - return cloudInfoSchema.parse(await res.json()); + return cloudInfoSchema.create(await res.json()); }, [props.config]) ); return ( diff --git a/packages/keystatic/src/component-blocks/cloud-image-preview.tsx b/packages/keystatic/src/component-blocks/cloud-image-preview.tsx index 99e94248a..90ec28770 100644 --- a/packages/keystatic/src/component-blocks/cloud-image-preview.tsx +++ b/packages/keystatic/src/component-blocks/cloud-image-preview.tsx @@ -1,7 +1,7 @@ import { useOverlayTriggerState } from '@react-stately/overlays'; import { useEffect, useState } from 'react'; import { useSelected, useSlateStatic } from 'slate-react'; -import { z } from 'zod'; +import * as s from 'superstruct'; import { ActionButton, @@ -67,10 +67,10 @@ function slugify(input: string) { return slug; } -const imageUploadResponse = z.object({ - src: z.string(), - width: z.number(), - height: z.number(), +const imageUploadResponse = s.type({ + src: s.string(), + width: s.number(), + height: s.number(), }); function uploadImage(file: File, config: Config) { @@ -110,11 +110,14 @@ function uploadImage(file: File, config: Config) { throw new Error(`Failed to upload image: ${await res.text()}`); } const data = await res.json(); - const parsedData = imageUploadResponse.safeParse(data); - if (!parsedData.success) { + + let parsedData; + try { + parsedData = imageUploadResponse.create(data); + } catch { throw new Error('Unexpected response from cloud'); } - return parsedData.data; + return parsedData; })(); } @@ -187,11 +190,11 @@ function loadImageDimensions(url: string) { }); } -const imageDataSchema = z.object({ - src: z.string(), - alt: z.string(), - width: z.number(), - height: z.number(), +const imageDataSchema = s.type({ + src: s.string(), + alt: s.string(), + width: s.number(), + height: s.number(), }); export async function loadImageData( @@ -211,10 +214,9 @@ export async function loadImageData( ); if (res.ok) { const data = await res.json(); - const parsed = imageDataSchema.safeParse(data); - if (parsed.success) { - return parsed.data; - } + try { + return imageDataSchema.create(data); + } catch {} } } return loadImageDimensions(url).then(dimensions => ({ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index db92d6001..ef502cdeb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1315,6 +1315,9 @@ importers: slate-react: specifier: ^0.91.9 version: 0.91.11(react-dom@18.2.0)(react@18.2.0)(slate@0.91.4) + superstruct: + specifier: ^1.0.4 + version: 1.0.4 unist-util-visit: specifier: ^5.0.0 version: 5.0.0 @@ -1330,9 +1333,6 @@ importers: yjs: specifier: ^13.6.11 version: 13.6.11 - zod: - specifier: ^3.20.2 - version: 3.22.2 devDependencies: '@jest/expect': specifier: ^29.7.0 @@ -24258,6 +24258,11 @@ packages: resolution: {integrity: sha512-W4SitSZ9MOyMPbHreoZVEneSZyPEeNGbdfJo/7FkJyRs/M3wQRFzq+t3S/NBwlrFSWdx1ONLjLb9pB+UKe4IqQ==} dev: true + /superstruct@1.0.4: + resolution: {integrity: sha512-7JpaAoX2NGyoFlI9NBh66BQXGONc+uE+MRS5i2iOBKuS4e+ccgMDjATgZldkah+33DakBxDHiss9kvUcGAO8UQ==} + engines: {node: '>=14.0.0'} + dev: false + /supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} @@ -26229,10 +26234,6 @@ packages: stacktracey: 2.1.8 dev: false - /zod@3.22.2: - resolution: {integrity: sha512-wvWkphh5WQsJbVk1tbx1l1Ly4yg+XecD+Mq280uBGt9wa5BKSWf4Mhp6GmrkPixhMxmabYY7RbzlwVP32pbGCg==} - dev: false - /zod@3.22.4: resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==}