diff --git a/package.json b/package.json index 1bd04050..bb22e98b 100755 --- a/package.json +++ b/package.json @@ -89,7 +89,7 @@ "swr": "^2.3.6", "typescript-eslint": "^8.40.0", "vite": "^7.1.3", - "zod": "^3.25.67", + "zod": "^4.1.5", "zustand": "^5.0.8" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6fa4cdcf..c782e7a3 100755 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -183,8 +183,8 @@ importers: specifier: ^7.1.3 version: 7.1.3(@types/node@24.3.0)(jiti@2.5.1)(sass@1.90.0)(sugarss@5.0.1(postcss@8.5.6))(tsx@4.20.4) zod: - specifier: ^3.25.67 - version: 3.25.67 + specifier: ^4.1.5 + version: 4.1.5 zustand: specifier: ^5.0.8 version: 5.0.8(@types/react@19.1.10)(react@19.1.1)(use-sync-external-store@1.5.0(react@19.1.1)) @@ -4895,8 +4895,8 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} - zod@3.25.67: - resolution: {integrity: sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==} + zod@4.1.5: + resolution: {integrity: sha512-rcUUZqlLJgBC33IT3PNMgsCq6TzLQEG/Ei/KTCU0PedSWRMAXoOUN+4t/0H+Q8bdnLPdqUYnvboJT0bn/229qg==} zustand@5.0.8: resolution: {integrity: sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw==} @@ -10437,7 +10437,7 @@ snapshots: yocto-queue@0.1.0: {} - zod@3.25.67: {} + zod@4.1.5: {} zustand@5.0.8(@types/react@19.1.10)(react@19.1.1)(use-sync-external-store@1.5.0(react@19.1.1)): optionalDependencies: diff --git a/src/lib/config/validate.ts b/src/lib/config/validate.ts index 04c0dfe9..46183b58 100755 --- a/src/lib/config/validate.ts +++ b/src/lib/config/validate.ts @@ -1,6 +1,6 @@ import { tmpdir } from 'os'; import { join, resolve } from 'path'; -import { type ZodIssue, z } from 'zod'; +import { z } from 'zod'; import { log } from '../logger'; import { ParsedConfig } from './read'; import { PROP_TO_ENV } from './read/env'; @@ -19,7 +19,7 @@ declare global { export const discordContent = z .object({ - webhookUrl: z.string().url().nullable().default(null), + webhookUrl: z.url().nullable().default(null), username: z.string().nullable().default(null), avatarUrl: z.string().nullable().default(null), content: z.string().nullable().default(null), @@ -63,10 +63,11 @@ export const schema = z.object({ inclusive: true, message: 'Secret must contain at least 32 characters', exact: false, + origin: 'string', }); } }), - databaseUrl: z.string().url(), + databaseUrl: z.url(), returnHttpsUrls: z.boolean().default(false), defaultDomain: z.string().nullable().default(null), tempDirectory: z @@ -135,7 +136,7 @@ export const schema = z.object({ if (s.type === 's3' && !s.s3) { for (const key of ['accessKeyId', 'secretAccessKey', 'region', 'bucket']) { c.addIssue({ - code: z.ZodIssueCode.invalid_type, + code: 'invalid_type', expected: 'string', received: 'unknown', path: ['s3', key], @@ -143,7 +144,7 @@ export const schema = z.object({ } } else if (s.type === 'local' && !s.local) { c.addIssue({ - code: z.ZodIssueCode.invalid_type, + code: 'invalid_type', expected: 'string', received: 'unknown', path: ['local', 'directory'], @@ -168,7 +169,7 @@ export const schema = z.object({ showUserSpecific: z.boolean().default(true), }), versionChecking: z.boolean().default(true), - versionAPI: z.string().url().default('https://zipline-version.diced.sh/'), + versionAPI: z.url().default('https://zipline-version.diced.sh/'), }), domains: z.array(z.string()).default([]), invites: z.object({ @@ -177,12 +178,12 @@ export const schema = z.object({ }), website: z.object({ title: z.string().default('Zipline'), - titleLogo: z.string().url().nullable().default(null), + titleLogo: z.url().nullable().default(null), externalLinks: z .array( z.object({ name: z.string(), - url: z.string().url(), + url: z.url(), }), ) .default([ @@ -195,7 +196,7 @@ export const schema = z.object({ url: 'https://zipline.diced.sh', }, ]), - loginBackground: z.string().url().nullable().default(null), + loginBackground: z.url().nullable().default(null), loginBackgroundBlur: z.boolean().default(true), defaultAvatar: z .string() @@ -228,7 +229,7 @@ export const schema = z.object({ .object({ clientId: z.string(), clientSecret: z.string(), - redirectUri: z.string().url().nullable().default(null), + redirectUri: z.url().nullable().default(null), allowedIds: z.array(z.string()).default([]), deniedIds: z.array(z.string()).default([]), }) @@ -245,7 +246,7 @@ export const schema = z.object({ .object({ clientId: z.string(), clientSecret: z.string(), - redirectUri: z.string().url().nullable().default(null), + redirectUri: z.url().nullable().default(null), }) .or( z.object({ @@ -258,7 +259,7 @@ export const schema = z.object({ .object({ clientId: z.string(), clientSecret: z.string(), - redirectUri: z.string().url().nullable().default(null), + redirectUri: z.url().nullable().default(null), }) .or( z.object({ @@ -271,10 +272,10 @@ export const schema = z.object({ .object({ clientId: z.string(), clientSecret: z.string(), - authorizeUrl: z.string().url(), - userinfoUrl: z.string().url(), - tokenUrl: z.string().url(), - redirectUri: z.string().url().nullable().default(null), + authorizeUrl: z.url(), + userinfoUrl: z.url(), + tokenUrl: z.url(), + redirectUri: z.url().nullable().default(null), }) .or( z.object({ @@ -289,9 +290,9 @@ export const schema = z.object({ }), discord: z .object({ - webhookUrl: z.string().url().nullable().default(null), + webhookUrl: z.url().nullable().default(null), username: z.string().nullable().default(null), - avatarUrl: z.string().url().nullable().default(null), + avatarUrl: z.url().nullable().default(null), onUpload: discordContent, onShorten: discordContent, }) @@ -309,8 +310,8 @@ export const schema = z.object({ allowList: z.array(z.string()).default([]), }), httpWebhook: z.object({ - onUpload: z.string().url().nullable().default(null), - onShorten: z.string().url().nullable().default(null), + onUpload: z.url().nullable().default(null), + onShorten: z.url().nullable().default(null), }), ssl: z.object({ key: z @@ -351,7 +352,7 @@ export function validateConfigObject(env: ParsedConfig): Config { if (!validated.success) { logger.error('There was an error while validating the environment.'); - for (const error of validated.error.errors) { + for (const error of validated.error.issues) { handleError(error); } @@ -363,12 +364,12 @@ export function validateConfigObject(env: ParsedConfig): Config { return validated.data; } -function handleError(error: ZodIssue) { +function handleError(error: z.core.$ZodIssue) { logger.debug(JSON.stringify(error)); if (error.code === 'invalid_union') { - for (const unionError of error.unionErrors) { - for (const subError of unionError.issues) { + for (const unionError of error.errors) { + for (const subError of unionError) { handleError(subError); } } @@ -378,7 +379,7 @@ function handleError(error: ZodIssue) { const path = error.path[1] === 'externalLinks' - ? `WEBSITE_EXTERNAL_LINKS[${error.path[2]}]` + ? `WEBSITE_EXTERNAL_LINKS[${String(error.path[2])}]` : (PROP_TO_ENV[error.path.join('.')] ?? error.path.join('.')); logger.error(`${path}: ${error.message}`); diff --git a/src/lib/import/version3/validateExport.ts b/src/lib/import/version3/validateExport.ts index c6809f0d..86b27ef3 100644 --- a/src/lib/import/version3/validateExport.ts +++ b/src/lib/import/version3/validateExport.ts @@ -167,17 +167,18 @@ export const export3Schema = z.object({ hostname: z.string(), release: z.string(), }), - env: z.record(z.string()), + env: z.record(z.string(), z.string()), }), - user_map: z.record(z.string()), - thumbnail_map: z.record(z.string()), - folder_map: z.record(z.string()), - file_map: z.record(z.string()), - url_map: z.record(z.string()), - invite_map: z.record(z.string()), + user_map: z.record(z.string(), z.string()), + thumbnail_map: z.record(z.string(), z.string()), + folder_map: z.record(z.string(), z.string()), + file_map: z.record(z.string(), z.string()), + url_map: z.record(z.string(), z.string()), + invite_map: z.record(z.string(), z.string()), users: z.record( + z.string(), z.object({ username: z.string(), password: z.string().optional().nullable(), @@ -204,6 +205,7 @@ export const export3Schema = z.object({ ), files: z.record( + z.string(), z.object({ name: z.string(), original_name: z.string().optional().nullable(), @@ -221,6 +223,7 @@ export const export3Schema = z.object({ ), thumbnails: z.record( + z.string(), z.object({ name: z.string(), created_at: z.string(), @@ -228,6 +231,7 @@ export const export3Schema = z.object({ ), folders: z.record( + z.string(), z.object({ name: z.string(), public: z.boolean(), @@ -238,6 +242,7 @@ export const export3Schema = z.object({ ), urls: z.record( + z.string(), z.object({ destination: z.string(), vanity: z.string().optional().nullable(), @@ -250,6 +255,7 @@ export const export3Schema = z.object({ ), invites: z.record( + z.string(), z.object({ code: z.string(), expires_at: z.string().optional().nullable(), diff --git a/src/server/routes/api/server/settings/index.ts b/src/server/routes/api/server/settings/index.ts index 97fa5ec5..942a6bc3 100644 --- a/src/server/routes/api/server/settings/index.ts +++ b/src/server/routes/api/server/settings/index.ts @@ -189,25 +189,25 @@ export default fastifyPlugin( featuresMetricsShowUserSpecific: z.boolean(), featuresVersionChecking: z.boolean(), - featuresVersionAPI: z.string().url(), + featuresVersionAPI: z.url(), invitesEnabled: z.boolean(), invitesLength: z.number().min(1).max(64), websiteTitle: z.string(), - websiteTitleLogo: z.string().url().nullable(), + websiteTitleLogo: z.url().nullable(), websiteExternalLinks: z .union([ z.array( z.object({ name: z.string(), - url: z.string().url(), + url: z.url(), }), ), z.string(), ]) .transform((value) => (typeof value === 'string' ? JSON.parse(value) : value)), - websiteLoginBackground: z.string().url().nullable(), + websiteLoginBackground: z.url().nullable(), websiteLoginBackgroundBlur: z.boolean(), websiteDefaultAvatar: z .string() @@ -241,7 +241,7 @@ export default fastifyPlugin( oauthDiscordClientId: z.string().nullable(), oauthDiscordClientSecret: z.string().nullable(), - oauthDiscordRedirectUri: z.string().url().endsWith('/api/auth/oauth/discord').nullable(), + oauthDiscordRedirectUri: z.url().endsWith('/api/auth/oauth/discord').nullable(), oauthDiscordAllowedIds: z .union([ z.array(z.string().refine((s) => /^\d+$/.test(s), 'Discord ID must be a number')), @@ -265,18 +265,18 @@ export default fastifyPlugin( oauthGoogleClientId: z.string().nullable(), oauthGoogleClientSecret: z.string().nullable(), - oauthGoogleRedirectUri: z.string().url().endsWith('/api/auth/oauth/google').nullable(), + oauthGoogleRedirectUri: z.url().endsWith('/api/auth/oauth/google').nullable(), oauthGithubClientId: z.string().nullable(), oauthGithubClientSecret: z.string().nullable(), - oauthGithubRedirectUri: z.string().url().endsWith('/api/auth/oauth/github').nullable(), + oauthGithubRedirectUri: z.url().endsWith('/api/auth/oauth/github').nullable(), oauthOidcClientId: z.string().nullable(), oauthOidcClientSecret: z.string().nullable(), - oauthOidcAuthorizeUrl: z.string().url().nullable(), - oauthOidcTokenUrl: z.string().url().nullable(), - oauthOidcUserinfoUrl: z.string().url().nullable(), - oauthOidcRedirectUri: z.string().url().endsWith('/api/auth/oauth/oidc').nullable(), + oauthOidcAuthorizeUrl: z.url().nullable(), + oauthOidcTokenUrl: z.url().nullable(), + oauthOidcUserinfoUrl: z.url().nullable(), + oauthOidcRedirectUri: z.url().endsWith('/api/auth/oauth/oidc').nullable(), mfaTotpEnabled: z.boolean(), mfaTotpIssuer: z.string(), @@ -290,22 +290,22 @@ export default fastifyPlugin( .union([z.array(z.string()), z.string()]) .transform((value) => (typeof value === 'string' ? value.split(',') : value)), - httpWebhookOnUpload: z.string().url().nullable(), - httpWebhookOnShorten: z.string().url().nullable(), + httpWebhookOnUpload: z.url().nullable(), + httpWebhookOnShorten: z.url().nullable(), - discordWebhookUrl: z.string().url().nullable(), + discordWebhookUrl: z.url().nullable(), discordUsername: z.string().nullable(), - discordAvatarUrl: z.string().url().nullable(), + discordAvatarUrl: z.url().nullable(), - discordOnUploadWebhookUrl: z.string().url().nullable(), + discordOnUploadWebhookUrl: z.url().nullable(), discordOnUploadUsername: z.string().nullable(), - discordOnUploadAvatarUrl: z.string().url().nullable(), + discordOnUploadAvatarUrl: z.url().nullable(), discordOnUploadContent: z.string().nullable(), discordOnUploadEmbed: discordEmbed, - discordOnShortenWebhookUrl: z.string().url().nullable(), + discordOnShortenWebhookUrl: z.url().nullable(), discordOnShortenUsername: z.string().nullable(), - discordOnShortenAvatarUrl: z.string().url().nullable(), + discordOnShortenAvatarUrl: z.url().nullable(), discordOnShortenContent: z.string().nullable(), discordOnShortenEmbed: discordEmbed,