feat: remove joi schema validation (#7226)
We do not really need runtime joi schema validation - this is what TypeScript is for. If people are ignoring TypeScript errors in your schema, or JavaScript errors, that is their fault and does not warrant an extra dependency (joi), lots of code to maintain, as well as slower startups. If we wanna keep runtime schema validation, we should switch to zod so that we can generate TypeScript types based on the schema and do not have to manually maintain config properties in 2 different places (types & schema). **joi PROs:** - Safety for JavaScript-only evangelists messing up their schema - Safety for people putting @ts-expect-error or `as any` everywhere in their code **joi CONs:** - Larger bundle size - More Modules - Slower Compilation Speed in dev. Worse DX - Slower Startup (it needs to validate) in dev. Worse DX - More code to maintain. For every schema change we'll have to change the types AND the joi schema - TypeScript already throws proper errors if you mess up your schema. Why have runtime errors? - The errors are bad. They might tell you what field has an issue, but they do not tell you what exactly is wrong. You have probably seen those "Field XY, value is incorrect" errors - and value could mean anything. Worse DX - Having extra properties in your schema, even if they are useless, doesn't cause any harm Cons outweigh the pros
This commit is contained in:
@@ -15,7 +15,6 @@ async function build() {
|
|||||||
splitting: false,
|
splitting: false,
|
||||||
external: [
|
external: [
|
||||||
'lodash',
|
'lodash',
|
||||||
//'joi',
|
|
||||||
'*.scss',
|
'*.scss',
|
||||||
'*.css',
|
'*.css',
|
||||||
'@payloadcms/translations',
|
'@payloadcms/translations',
|
||||||
|
|||||||
@@ -99,7 +99,6 @@
|
|||||||
"get-tsconfig": "^4.7.2",
|
"get-tsconfig": "^4.7.2",
|
||||||
"http-status": "1.6.2",
|
"http-status": "1.6.2",
|
||||||
"image-size": "^1.1.1",
|
"image-size": "^1.1.1",
|
||||||
"joi": "^17.12.1",
|
|
||||||
"json-schema-to-typescript": "11.0.3",
|
"json-schema-to-typescript": "11.0.3",
|
||||||
"jsonwebtoken": "9.0.1",
|
"jsonwebtoken": "9.0.1",
|
||||||
"minimist": "1.2.8",
|
"minimist": "1.2.8",
|
||||||
@@ -117,7 +116,6 @@
|
|||||||
"@monaco-editor/react": "4.5.1",
|
"@monaco-editor/react": "4.5.1",
|
||||||
"@payloadcms/eslint-config": "workspace:*",
|
"@payloadcms/eslint-config": "workspace:*",
|
||||||
"@types/express-fileupload": "1.4.1",
|
"@types/express-fileupload": "1.4.1",
|
||||||
"@types/joi": "14.3.4",
|
|
||||||
"@types/json-schema": "7.0.15",
|
"@types/json-schema": "7.0.15",
|
||||||
"@types/jsonwebtoken": "8.5.9",
|
"@types/jsonwebtoken": "8.5.9",
|
||||||
"@types/minimist": "1.2.2",
|
"@types/minimist": "1.2.2",
|
||||||
|
|||||||
@@ -1,240 +0,0 @@
|
|||||||
import joi from 'joi'
|
|
||||||
|
|
||||||
import { endpointsSchema } from '../../config/schema.js'
|
|
||||||
import {
|
|
||||||
componentSchema,
|
|
||||||
customViewSchema,
|
|
||||||
livePreviewSchema,
|
|
||||||
} from '../../config/shared/componentSchema.js'
|
|
||||||
import { openGraphSchema } from '../../config/shared/openGraphSchema.js'
|
|
||||||
|
|
||||||
const collectionSchema = joi.object().keys({
|
|
||||||
slug: joi.string().required(),
|
|
||||||
access: joi.object({
|
|
||||||
admin: joi.func(),
|
|
||||||
create: joi.func(),
|
|
||||||
delete: joi.func(),
|
|
||||||
read: joi.func(),
|
|
||||||
readVersions: joi.func(),
|
|
||||||
unlock: joi.func(),
|
|
||||||
update: joi.func(),
|
|
||||||
}),
|
|
||||||
admin: joi.object({
|
|
||||||
components: joi.object({
|
|
||||||
afterList: joi.array().items(componentSchema),
|
|
||||||
afterListTable: joi.array().items(componentSchema),
|
|
||||||
beforeList: joi.array().items(componentSchema),
|
|
||||||
beforeListTable: joi.array().items(componentSchema),
|
|
||||||
edit: joi.object({
|
|
||||||
Description: componentSchema,
|
|
||||||
PreviewButton: componentSchema,
|
|
||||||
PublishButton: componentSchema,
|
|
||||||
SaveButton: componentSchema,
|
|
||||||
SaveDraftButton: componentSchema,
|
|
||||||
Upload: componentSchema,
|
|
||||||
}),
|
|
||||||
views: joi.object({
|
|
||||||
Edit: joi.alternatives().try(
|
|
||||||
componentSchema,
|
|
||||||
joi.object({
|
|
||||||
API: joi.alternatives().try(componentSchema, customViewSchema),
|
|
||||||
Default: joi.alternatives().try(componentSchema, customViewSchema),
|
|
||||||
LivePreview: joi.alternatives().try(componentSchema, customViewSchema),
|
|
||||||
Version: joi.alternatives().try(componentSchema, customViewSchema),
|
|
||||||
Versions: joi.alternatives().try(componentSchema, customViewSchema),
|
|
||||||
// Relationships
|
|
||||||
// References
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
List: joi.alternatives().try(
|
|
||||||
componentSchema,
|
|
||||||
joi.object({
|
|
||||||
Component: componentSchema,
|
|
||||||
actions: joi.array().items(componentSchema),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
custom: joi.object().pattern(joi.string(), joi.any()),
|
|
||||||
defaultColumns: joi.array().items(joi.string()),
|
|
||||||
description: joi
|
|
||||||
.alternatives()
|
|
||||||
.try(joi.func(), joi.object().pattern(joi.string(), [joi.string()]), joi.string()),
|
|
||||||
enableRichTextLink: joi.boolean(),
|
|
||||||
enableRichTextRelationship: joi.boolean(),
|
|
||||||
group: joi.alternatives().try(joi.string(), joi.object().pattern(joi.string(), [joi.string()])),
|
|
||||||
hidden: joi.alternatives().try(joi.boolean(), joi.func()),
|
|
||||||
hideAPIURL: joi.bool(),
|
|
||||||
listSearchableFields: joi.array().items(joi.string()),
|
|
||||||
livePreview: joi.object(livePreviewSchema),
|
|
||||||
meta: joi.object({
|
|
||||||
description: joi.string(),
|
|
||||||
openGraph: openGraphSchema,
|
|
||||||
}),
|
|
||||||
pagination: joi.object({
|
|
||||||
defaultLimit: joi.number(),
|
|
||||||
limits: joi.array().items(joi.number()),
|
|
||||||
}),
|
|
||||||
preview: joi.func(),
|
|
||||||
useAsTitle: joi.string(),
|
|
||||||
}),
|
|
||||||
auth: joi.alternatives().try(
|
|
||||||
joi.object({
|
|
||||||
cookies: joi.object().keys({
|
|
||||||
domain: joi.string(),
|
|
||||||
sameSite: joi.string(), // TODO: add further specificity with joi.xor
|
|
||||||
secure: joi.boolean(),
|
|
||||||
}),
|
|
||||||
depth: joi.number(),
|
|
||||||
disableLocalStrategy: joi.boolean().valid(true),
|
|
||||||
forgotPassword: joi.object().keys({
|
|
||||||
generateEmailHTML: joi.func(),
|
|
||||||
generateEmailSubject: joi.func(),
|
|
||||||
}),
|
|
||||||
lockTime: joi.number(),
|
|
||||||
loginWithUsername: joi.alternatives().try(
|
|
||||||
joi.boolean(),
|
|
||||||
joi.object().keys({
|
|
||||||
allowEmailLogin: joi.boolean(),
|
|
||||||
requireEmail: joi.boolean(),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
maxLoginAttempts: joi.number(),
|
|
||||||
removeTokenFromResponses: joi.boolean().valid(true),
|
|
||||||
strategies: joi.array().items(
|
|
||||||
joi.object().keys({
|
|
||||||
name: joi.string().required(),
|
|
||||||
authenticate: joi.func().required(),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
tokenExpiration: joi.number(),
|
|
||||||
useAPIKey: joi.boolean(),
|
|
||||||
verify: joi.alternatives().try(
|
|
||||||
joi.boolean(),
|
|
||||||
joi.object().keys({
|
|
||||||
generateEmailHTML: joi.func(),
|
|
||||||
generateEmailSubject: joi.func(),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
joi.boolean(),
|
|
||||||
),
|
|
||||||
custom: joi.object().pattern(joi.string(), joi.any()),
|
|
||||||
dbName: joi.alternatives().try(joi.string(), joi.func()),
|
|
||||||
defaultSort: joi.string(),
|
|
||||||
disableDuplicate: joi.bool(),
|
|
||||||
endpoints: endpointsSchema,
|
|
||||||
fields: joi.array(),
|
|
||||||
graphQL: joi.alternatives().try(
|
|
||||||
joi.object().keys({
|
|
||||||
pluralName: joi.string(),
|
|
||||||
singularName: joi.string(),
|
|
||||||
}),
|
|
||||||
joi.boolean(),
|
|
||||||
),
|
|
||||||
hooks: joi.object({
|
|
||||||
afterChange: joi.array().items(joi.func()),
|
|
||||||
afterDelete: joi.array().items(joi.func()),
|
|
||||||
afterForgotPassword: joi.array().items(joi.func()),
|
|
||||||
afterLogin: joi.array().items(joi.func()),
|
|
||||||
afterLogout: joi.array().items(joi.func()),
|
|
||||||
afterMe: joi.array().items(joi.func()),
|
|
||||||
afterOperation: joi.array().items(joi.func()),
|
|
||||||
afterRead: joi.array().items(joi.func()),
|
|
||||||
afterRefresh: joi.array().items(joi.func()),
|
|
||||||
beforeChange: joi.array().items(joi.func()),
|
|
||||||
beforeDelete: joi.array().items(joi.func()),
|
|
||||||
beforeLogin: joi.array().items(joi.func()),
|
|
||||||
beforeOperation: joi.array().items(joi.func()),
|
|
||||||
beforeRead: joi.array().items(joi.func()),
|
|
||||||
beforeValidate: joi.array().items(joi.func()),
|
|
||||||
me: joi.array().items(joi.func()),
|
|
||||||
refresh: joi.array().items(joi.func()),
|
|
||||||
}),
|
|
||||||
labels: joi.object({
|
|
||||||
plural: joi
|
|
||||||
.alternatives()
|
|
||||||
.try(joi.func(), joi.string(), joi.object().pattern(joi.string(), [joi.string()])),
|
|
||||||
singular: joi
|
|
||||||
.alternatives()
|
|
||||||
.try(joi.func(), joi.string(), joi.object().pattern(joi.string(), [joi.string()])),
|
|
||||||
}),
|
|
||||||
timestamps: joi.boolean(),
|
|
||||||
typescript: joi.object().keys({
|
|
||||||
interface: joi.string(),
|
|
||||||
}),
|
|
||||||
upload: joi.alternatives().try(
|
|
||||||
joi.object({
|
|
||||||
adapter: joi.string(),
|
|
||||||
adminThumbnail: joi.alternatives().try(joi.string(), componentSchema),
|
|
||||||
crop: joi.bool(),
|
|
||||||
disableLocalStorage: joi.bool(),
|
|
||||||
externalFileHeaderFilter: joi.func(),
|
|
||||||
filesRequiredOnCreate: joi.bool(),
|
|
||||||
focalPoint: joi.bool(),
|
|
||||||
formatOptions: joi.object().keys({
|
|
||||||
format: joi.string(),
|
|
||||||
options: joi.object(),
|
|
||||||
}),
|
|
||||||
handlers: joi.array().items(joi.func()),
|
|
||||||
imageSizes: joi.array().items(
|
|
||||||
joi
|
|
||||||
.object()
|
|
||||||
.keys({
|
|
||||||
name: joi.string(),
|
|
||||||
crop: joi.string(), // TODO: add further specificity with joi.xor
|
|
||||||
height: joi.number().integer().allow(null),
|
|
||||||
width: joi.number().integer().allow(null),
|
|
||||||
})
|
|
||||||
.unknown(),
|
|
||||||
),
|
|
||||||
mimeTypes: joi.array().items(joi.string()),
|
|
||||||
modifyResponseHeaders: joi.func(),
|
|
||||||
resizeOptions: joi
|
|
||||||
.object()
|
|
||||||
.keys({
|
|
||||||
background: joi.string(),
|
|
||||||
fastShrinkOnLoad: joi.bool(),
|
|
||||||
fit: joi.string(),
|
|
||||||
height: joi.number().allow(null),
|
|
||||||
kernel: joi.string(),
|
|
||||||
position: joi.alternatives().try(joi.string(), joi.number()),
|
|
||||||
width: joi.number().allow(null),
|
|
||||||
withoutEnlargement: joi.bool(),
|
|
||||||
})
|
|
||||||
.allow(null),
|
|
||||||
staticDir: joi.string(),
|
|
||||||
tempFileDir: joi.string(),
|
|
||||||
trimOptions: joi.alternatives().try(
|
|
||||||
joi.object().keys({
|
|
||||||
format: joi.string(),
|
|
||||||
options: joi.object(),
|
|
||||||
}),
|
|
||||||
joi.string(),
|
|
||||||
joi.number(),
|
|
||||||
),
|
|
||||||
useTempFiles: joi.bool(),
|
|
||||||
}),
|
|
||||||
joi.boolean(),
|
|
||||||
),
|
|
||||||
versions: joi.alternatives().try(
|
|
||||||
joi.object({
|
|
||||||
drafts: joi.alternatives().try(
|
|
||||||
joi.object({
|
|
||||||
autosave: joi.alternatives().try(
|
|
||||||
joi.boolean(),
|
|
||||||
joi.object({
|
|
||||||
interval: joi.number(),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
validate: joi.boolean(),
|
|
||||||
}),
|
|
||||||
joi.boolean(),
|
|
||||||
),
|
|
||||||
maxPerDoc: joi.number(),
|
|
||||||
}),
|
|
||||||
joi.boolean(),
|
|
||||||
),
|
|
||||||
})
|
|
||||||
|
|
||||||
export default collectionSchema
|
|
||||||
@@ -1,217 +0,0 @@
|
|||||||
import joi from 'joi'
|
|
||||||
|
|
||||||
import { adminViewSchema } from './shared/adminViewSchema.js'
|
|
||||||
import { componentSchema, livePreviewSchema } from './shared/componentSchema.js'
|
|
||||||
import { openGraphSchema } from './shared/openGraphSchema.js'
|
|
||||||
|
|
||||||
const component = joi.alternatives().try(joi.object().unknown(), joi.func())
|
|
||||||
|
|
||||||
export const endpointsSchema = joi.alternatives().try(
|
|
||||||
joi.array().items(
|
|
||||||
joi.object({
|
|
||||||
custom: joi.object().pattern(joi.string(), joi.any()),
|
|
||||||
handler: joi.alternatives().try(joi.array().items(joi.func()), joi.func()),
|
|
||||||
method: joi
|
|
||||||
.string()
|
|
||||||
.valid('get', 'head', 'post', 'put', 'patch', 'delete', 'connect', 'options'),
|
|
||||||
path: joi.string(),
|
|
||||||
root: joi.bool(),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
joi.boolean(),
|
|
||||||
)
|
|
||||||
|
|
||||||
export default joi.object({
|
|
||||||
admin: joi.object({
|
|
||||||
autoLogin: joi.alternatives().try(
|
|
||||||
joi.object().keys({
|
|
||||||
email: joi.string(),
|
|
||||||
password: joi.string(),
|
|
||||||
prefillOnly: joi.boolean(),
|
|
||||||
username: joi.string(),
|
|
||||||
}),
|
|
||||||
joi.boolean(),
|
|
||||||
),
|
|
||||||
avatar: joi.alternatives().try(joi.string(), component),
|
|
||||||
buildPath: joi.string(),
|
|
||||||
components: joi.object().keys({
|
|
||||||
Nav: component,
|
|
||||||
actions: joi.array().items(component),
|
|
||||||
afterDashboard: joi.array().items(component),
|
|
||||||
afterLogin: joi.array().items(component),
|
|
||||||
afterNavLinks: joi.array().items(component),
|
|
||||||
beforeDashboard: joi.array().items(component),
|
|
||||||
beforeLogin: joi.array().items(component),
|
|
||||||
beforeNavLinks: joi.array().items(component),
|
|
||||||
graphics: joi.object({
|
|
||||||
Icon: component,
|
|
||||||
Logo: component,
|
|
||||||
}),
|
|
||||||
logout: joi.object({
|
|
||||||
Button: component,
|
|
||||||
}),
|
|
||||||
providers: joi.array().items(component),
|
|
||||||
views: joi.alternatives().try(
|
|
||||||
joi.object({
|
|
||||||
Account: joi.alternatives().try(component, adminViewSchema),
|
|
||||||
Dashboard: joi.alternatives().try(component, adminViewSchema),
|
|
||||||
}),
|
|
||||||
joi.object().pattern(joi.string(), component),
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
custom: joi.object().pattern(joi.string(), joi.any()),
|
|
||||||
dateFormat: joi.string(),
|
|
||||||
disable: joi.bool(),
|
|
||||||
livePreview: joi.object({
|
|
||||||
...livePreviewSchema,
|
|
||||||
collections: joi.array().items(joi.string()),
|
|
||||||
globals: joi.array().items(joi.string()),
|
|
||||||
}),
|
|
||||||
meta: joi.object().keys({
|
|
||||||
defaultOGImageType: joi.string().valid('off', 'dynamic', 'static'),
|
|
||||||
description: joi.string(),
|
|
||||||
icons: joi.array().items(
|
|
||||||
joi.object().keys({
|
|
||||||
type: joi.string(),
|
|
||||||
color: joi.string(),
|
|
||||||
fetchPriority: joi.string().valid('auto', 'high', 'low'),
|
|
||||||
media: joi.string(),
|
|
||||||
rel: joi.string(),
|
|
||||||
sizes: joi.string(),
|
|
||||||
url: joi.string(),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
openGraph: openGraphSchema,
|
|
||||||
titleSuffix: joi.string(),
|
|
||||||
}),
|
|
||||||
routes: joi.object({
|
|
||||||
account: joi.string(),
|
|
||||||
createFirstUser: joi.string(),
|
|
||||||
forgot: joi.string(),
|
|
||||||
inactivity: joi.string(),
|
|
||||||
login: joi.string(),
|
|
||||||
logout: joi.string(),
|
|
||||||
reset: joi.string(),
|
|
||||||
unauthorized: joi.string(),
|
|
||||||
}),
|
|
||||||
user: joi.string(),
|
|
||||||
}),
|
|
||||||
bin: joi.array().items(
|
|
||||||
joi.object().keys({
|
|
||||||
key: joi.string(),
|
|
||||||
scriptPath: joi.string(),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
collections: joi.array(),
|
|
||||||
cookiePrefix: joi.string(),
|
|
||||||
cors: [
|
|
||||||
joi.string().valid('*'),
|
|
||||||
joi.array().items(joi.string()),
|
|
||||||
joi.object().keys({
|
|
||||||
headers: joi.array().items(joi.string()),
|
|
||||||
origins: [joi.string().valid('*'), joi.array().items(joi.string())],
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
csrf: joi.array().items(joi.string().allow('')).sparse(),
|
|
||||||
custom: joi.object().pattern(joi.string(), joi.any()),
|
|
||||||
db: joi.any(),
|
|
||||||
debug: joi.boolean(),
|
|
||||||
defaultDepth: joi.number().min(0).max(30),
|
|
||||||
defaultMaxTextLength: joi.number(),
|
|
||||||
editor: joi
|
|
||||||
.object()
|
|
||||||
.optional()
|
|
||||||
.keys({
|
|
||||||
CellComponent: componentSchema.optional(),
|
|
||||||
FieldComponent: componentSchema.optional(),
|
|
||||||
afterReadPromise: joi.func().optional(),
|
|
||||||
outputSchema: joi.func().optional(),
|
|
||||||
populationPromise: joi.func().optional(),
|
|
||||||
validate: joi.func().required(),
|
|
||||||
})
|
|
||||||
.unknown(),
|
|
||||||
email: joi.alternatives().try(joi.object(), joi.func()),
|
|
||||||
endpoints: endpointsSchema,
|
|
||||||
globals: joi.array(),
|
|
||||||
graphQL: joi.object().keys({
|
|
||||||
disable: joi.boolean(),
|
|
||||||
disablePlaygroundInProduction: joi.boolean(),
|
|
||||||
maxComplexity: joi.number(),
|
|
||||||
mutations: joi.function(),
|
|
||||||
queries: joi.function(),
|
|
||||||
schemaOutputFile: joi.string(),
|
|
||||||
}),
|
|
||||||
hooks: joi.object().keys({
|
|
||||||
afterError: joi.func(),
|
|
||||||
}),
|
|
||||||
i18n: joi.object(),
|
|
||||||
indexSortableFields: joi.boolean(),
|
|
||||||
local: joi.boolean(),
|
|
||||||
localization: joi.alternatives().try(
|
|
||||||
joi.object().keys({
|
|
||||||
defaultLocale: joi.string(),
|
|
||||||
fallback: joi.boolean(),
|
|
||||||
localeCodes: joi.array().items(joi.string()),
|
|
||||||
locales: joi.alternatives().try(
|
|
||||||
joi.array().items(
|
|
||||||
joi.object().keys({
|
|
||||||
code: joi.string(),
|
|
||||||
fallbackLocale: joi.string(),
|
|
||||||
label: joi
|
|
||||||
.alternatives()
|
|
||||||
.try(
|
|
||||||
joi.object().pattern(joi.string(), [joi.string()]),
|
|
||||||
joi.string(),
|
|
||||||
joi.valid(false),
|
|
||||||
),
|
|
||||||
rtl: joi.boolean(),
|
|
||||||
toString: joi.func(),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
joi.array().items(joi.string()),
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
joi.boolean(),
|
|
||||||
),
|
|
||||||
maxDepth: joi.number().min(0).max(100),
|
|
||||||
onInit: joi.func(),
|
|
||||||
plugins: joi.array().items(joi.func()),
|
|
||||||
routes: joi.object({
|
|
||||||
admin: joi.string(),
|
|
||||||
api: joi.string(),
|
|
||||||
graphQL: joi.string(),
|
|
||||||
graphQLPlayground: joi.string(),
|
|
||||||
}),
|
|
||||||
secret: joi.string(),
|
|
||||||
serverURL: joi
|
|
||||||
.string()
|
|
||||||
.uri()
|
|
||||||
.allow('')
|
|
||||||
.custom((value, helper) => {
|
|
||||||
const urlWithoutProtocol = value.split('//')[1]
|
|
||||||
|
|
||||||
if (!urlWithoutProtocol) {
|
|
||||||
return helper.message({
|
|
||||||
custom: 'You need to include either "https://" or "http://" in your serverURL.',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (urlWithoutProtocol.indexOf('/') > -1) {
|
|
||||||
return helper.message({
|
|
||||||
custom:
|
|
||||||
'Your serverURL cannot have a path. It can only contain a protocol, a domain, and an optional port.',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return value
|
|
||||||
}),
|
|
||||||
sharp: joi.any(),
|
|
||||||
telemetry: joi.boolean(),
|
|
||||||
typescript: joi.object({
|
|
||||||
autoGenerate: joi.boolean(),
|
|
||||||
declare: joi.alternatives().try(joi.boolean(), joi.object({ ignoreTSError: joi.boolean() })),
|
|
||||||
outputFile: joi.string(),
|
|
||||||
schema: joi.array().items(joi.func()),
|
|
||||||
}),
|
|
||||||
upload: joi.object(),
|
|
||||||
})
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import joi from 'joi'
|
|
||||||
|
|
||||||
import { componentSchema } from './componentSchema.js'
|
|
||||||
|
|
||||||
export const adminViewSchema = joi.array().items(
|
|
||||||
joi.object().keys({
|
|
||||||
Component: componentSchema,
|
|
||||||
exact: joi.bool(),
|
|
||||||
path: joi.string().required(),
|
|
||||||
sensitive: joi.bool(),
|
|
||||||
strict: joi.bool(),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
import joi from 'joi'
|
|
||||||
|
|
||||||
export const componentSchema = joi.alternatives().try(joi.object().unknown(), joi.func())
|
|
||||||
|
|
||||||
export const documentTabSchema = {
|
|
||||||
condition: joi.func(),
|
|
||||||
href: joi.alternatives().try(joi.string(), joi.func()).required(),
|
|
||||||
isActive: joi.alternatives().try(joi.func(), joi.boolean()),
|
|
||||||
label: joi.alternatives().try(joi.string(), joi.func()).required(),
|
|
||||||
newTab: joi.boolean(),
|
|
||||||
pillLabel: joi.alternatives().try(joi.string(), joi.func()),
|
|
||||||
}
|
|
||||||
|
|
||||||
export const customViewSchema = joi.object({
|
|
||||||
Component: componentSchema,
|
|
||||||
Tab: joi.alternatives().try(documentTabSchema, componentSchema),
|
|
||||||
actions: joi.array().items(componentSchema),
|
|
||||||
path: joi.string(),
|
|
||||||
})
|
|
||||||
|
|
||||||
export const livePreviewSchema = {
|
|
||||||
breakpoints: joi.array().items(
|
|
||||||
joi.object({
|
|
||||||
name: joi.string(),
|
|
||||||
height: joi.alternatives().try(joi.number(), joi.string()),
|
|
||||||
label: joi.string(),
|
|
||||||
width: joi.alternatives().try(joi.number(), joi.string()),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
url: joi.alternatives().try(joi.string(), joi.func()),
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import joi from 'joi'
|
|
||||||
|
|
||||||
const ogImageObj = joi.object({
|
|
||||||
type: joi.string(),
|
|
||||||
alt: joi.string(),
|
|
||||||
height: joi.alternatives().try(joi.string(), joi.number()),
|
|
||||||
url: joi.string(),
|
|
||||||
width: joi.alternatives().try(joi.string(), joi.number()),
|
|
||||||
})
|
|
||||||
|
|
||||||
export const openGraphSchema = joi.object({
|
|
||||||
description: joi.string(),
|
|
||||||
images: joi.alternatives().try(ogImageObj, joi.array().items(ogImageObj)),
|
|
||||||
siteName: joi.string(),
|
|
||||||
title: joi.string(),
|
|
||||||
})
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
import type { ValidationResult } from 'joi'
|
|
||||||
import type { Logger } from 'pino'
|
|
||||||
|
|
||||||
import type { SanitizedCollectionConfig } from '../collections/config/types.js'
|
|
||||||
import type { SanitizedGlobalConfig } from '../globals/config/types.js'
|
|
||||||
import type { SanitizedConfig } from './types.js'
|
|
||||||
|
|
||||||
import collectionSchema from '../collections/config/schema.js'
|
|
||||||
import fieldSchema, { idField } from '../fields/config/schema.js'
|
|
||||||
import { fieldAffectsData } from '../fields/config/types.js'
|
|
||||||
import globalSchema from '../globals/config/schema.js'
|
|
||||||
import schema from './schema.js'
|
|
||||||
|
|
||||||
const validateFields = (
|
|
||||||
context: string,
|
|
||||||
entity: SanitizedCollectionConfig | SanitizedGlobalConfig,
|
|
||||||
): string[] => {
|
|
||||||
const errors: string[] = []
|
|
||||||
entity.fields.forEach((field) => {
|
|
||||||
let idResult: Partial<ValidationResult> = { error: null }
|
|
||||||
if (fieldAffectsData(field) && field.name === 'id') {
|
|
||||||
idResult = idField.validate(field, { abortEarly: false })
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = fieldSchema.validate(field, { abortEarly: false })
|
|
||||||
if (idResult.error) {
|
|
||||||
idResult.error.details.forEach(({ message }) => {
|
|
||||||
errors.push(
|
|
||||||
`${context} "${entity.slug}" > Field${
|
|
||||||
fieldAffectsData(field) ? ` "${field.name}" >` : ''
|
|
||||||
} ${message}`,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (result.error) {
|
|
||||||
result.error.details.forEach(({ message }) => {
|
|
||||||
errors.push(
|
|
||||||
`${context} "${entity.slug}" > Field${
|
|
||||||
fieldAffectsData(field) ? ` "${field.name}" >` : ''
|
|
||||||
} ${message}`,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return errors
|
|
||||||
}
|
|
||||||
|
|
||||||
const validateCollections = (collections: SanitizedCollectionConfig[]): string[] => {
|
|
||||||
const errors: string[] = []
|
|
||||||
collections.forEach((collection) => {
|
|
||||||
const result = collectionSchema.validate(collection, { abortEarly: false })
|
|
||||||
if (result.error) {
|
|
||||||
result.error.details.forEach(({ message }) => {
|
|
||||||
errors.push(`Collection "${collection.slug}" > ${message}`)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
errors.push(...validateFields('Collection', collection))
|
|
||||||
})
|
|
||||||
|
|
||||||
return errors
|
|
||||||
}
|
|
||||||
|
|
||||||
const validateGlobals = (globals: SanitizedGlobalConfig[]): string[] => {
|
|
||||||
const errors: string[] = []
|
|
||||||
globals.forEach((global) => {
|
|
||||||
const result = globalSchema.validate(global, { abortEarly: false })
|
|
||||||
if (result.error) {
|
|
||||||
result.error.details.forEach(({ message }) => {
|
|
||||||
errors.push(`Globals "${global.slug}" > ${message}`)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
errors.push(...validateFields('Global', global))
|
|
||||||
})
|
|
||||||
|
|
||||||
return errors
|
|
||||||
}
|
|
||||||
|
|
||||||
export const validateSchema = (config: SanitizedConfig, logger: Logger): SanitizedConfig => {
|
|
||||||
const result = schema.validate(config, {
|
|
||||||
abortEarly: false,
|
|
||||||
})
|
|
||||||
|
|
||||||
const nestedErrors = [
|
|
||||||
...validateCollections(config.collections),
|
|
||||||
...validateGlobals(config.globals),
|
|
||||||
]
|
|
||||||
|
|
||||||
if (result.error || nestedErrors.length > 0) {
|
|
||||||
logger.error(
|
|
||||||
`There were ${
|
|
||||||
(result.error?.details?.length || 0) + nestedErrors.length
|
|
||||||
} errors validating your Payload config`,
|
|
||||||
)
|
|
||||||
|
|
||||||
let i = 0
|
|
||||||
if (result.error) {
|
|
||||||
result.error.details.forEach(({ message }) => {
|
|
||||||
i += 1
|
|
||||||
logger.error(`${i}: ${message}`)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
nestedErrors.forEach((message) => {
|
|
||||||
i += 1
|
|
||||||
logger.error(`${i}: ${message}`)
|
|
||||||
})
|
|
||||||
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.value
|
|
||||||
}
|
|
||||||
@@ -1,592 +0,0 @@
|
|||||||
import joi from 'joi'
|
|
||||||
|
|
||||||
import { componentSchema } from '../../config/shared/componentSchema.js'
|
|
||||||
|
|
||||||
export const baseAdminComponentFields = joi
|
|
||||||
.object()
|
|
||||||
.keys({
|
|
||||||
Cell: componentSchema,
|
|
||||||
Description: componentSchema,
|
|
||||||
Field: componentSchema,
|
|
||||||
Filter: componentSchema,
|
|
||||||
})
|
|
||||||
.default({})
|
|
||||||
|
|
||||||
export const baseAdminFields = joi.object().keys({
|
|
||||||
className: joi.string(),
|
|
||||||
components: baseAdminComponentFields,
|
|
||||||
condition: joi.func(),
|
|
||||||
custom: joi.object().pattern(joi.string(), joi.any()),
|
|
||||||
description: joi
|
|
||||||
.alternatives()
|
|
||||||
.try(joi.string(), joi.object().pattern(joi.string(), [joi.string()]), joi.function()),
|
|
||||||
disableBulkEdit: joi.boolean().default(false),
|
|
||||||
disableListColumn: joi.boolean().default(false),
|
|
||||||
disableListFilter: joi.boolean().default(false),
|
|
||||||
disabled: joi.boolean().default(false),
|
|
||||||
hidden: joi.boolean().default(false),
|
|
||||||
initCollapsed: joi.boolean().default(false),
|
|
||||||
position: joi.string().valid('sidebar'),
|
|
||||||
readOnly: joi.boolean().default(false),
|
|
||||||
style: joi.object().unknown(),
|
|
||||||
width: joi.string(),
|
|
||||||
})
|
|
||||||
|
|
||||||
export const baseField = joi
|
|
||||||
.object()
|
|
||||||
.keys({
|
|
||||||
access: joi.object().keys({
|
|
||||||
create: joi.func(),
|
|
||||||
read: joi.func(),
|
|
||||||
update: joi.func(),
|
|
||||||
}),
|
|
||||||
admin: baseAdminFields.default(),
|
|
||||||
custom: joi.object().pattern(joi.string(), joi.any()),
|
|
||||||
hidden: joi.boolean().default(false),
|
|
||||||
hooks: joi
|
|
||||||
.object()
|
|
||||||
.keys({
|
|
||||||
afterChange: joi.array().items(joi.func()).default([]),
|
|
||||||
afterRead: joi.array().items(joi.func()).default([]),
|
|
||||||
beforeChange: joi.array().items(joi.func()).default([]),
|
|
||||||
beforeDuplicate: joi.array().items(joi.func()).default([]),
|
|
||||||
beforeValidate: joi.array().items(joi.func()).default([]),
|
|
||||||
})
|
|
||||||
.default(),
|
|
||||||
index: joi.boolean().default(false),
|
|
||||||
label: joi
|
|
||||||
.alternatives()
|
|
||||||
.try(
|
|
||||||
joi.func(),
|
|
||||||
joi.object().pattern(joi.string(), [joi.string()]),
|
|
||||||
joi.string(),
|
|
||||||
joi.valid(false),
|
|
||||||
),
|
|
||||||
localized: joi.boolean().default(false),
|
|
||||||
required: joi.boolean().default(false),
|
|
||||||
saveToJWT: joi.alternatives().try(joi.boolean(), joi.string()).default(false),
|
|
||||||
typescriptSchema: joi.array().items(joi.func()),
|
|
||||||
unique: joi.boolean().default(false),
|
|
||||||
validate: joi.func(),
|
|
||||||
})
|
|
||||||
.default()
|
|
||||||
|
|
||||||
export const idField = baseField.keys({
|
|
||||||
name: joi.string().valid('id'),
|
|
||||||
type: joi.string().valid('text', 'number'),
|
|
||||||
localized: joi.invalid(true),
|
|
||||||
required: joi.not(false, 0).default(true),
|
|
||||||
})
|
|
||||||
|
|
||||||
export const text = baseField.keys({
|
|
||||||
name: joi.string().required(),
|
|
||||||
type: joi.string().valid('text').required(),
|
|
||||||
admin: baseAdminFields.keys({
|
|
||||||
autoComplete: joi.string(),
|
|
||||||
components: baseAdminComponentFields.keys({
|
|
||||||
Error: componentSchema,
|
|
||||||
Label: componentSchema,
|
|
||||||
afterInput: joi.array().items(componentSchema),
|
|
||||||
beforeInput: joi.array().items(componentSchema),
|
|
||||||
}),
|
|
||||||
placeholder: joi
|
|
||||||
.alternatives()
|
|
||||||
.try(joi.object().pattern(joi.string(), [joi.string()]), joi.string()),
|
|
||||||
rtl: joi.boolean(),
|
|
||||||
}),
|
|
||||||
defaultValue: joi.alternatives().try(joi.string().allow(''), joi.func()),
|
|
||||||
hasMany: joi.boolean().default(false),
|
|
||||||
maxLength: joi.number(),
|
|
||||||
maxRows: joi.number().when('hasMany', { is: joi.not(true), then: joi.forbidden() }),
|
|
||||||
minLength: joi.number(),
|
|
||||||
minRows: joi.number().when('hasMany', { is: joi.not(true), then: joi.forbidden() }),
|
|
||||||
})
|
|
||||||
|
|
||||||
export const number = baseField.keys({
|
|
||||||
name: joi.string().required(),
|
|
||||||
type: joi.string().valid('number').required(),
|
|
||||||
admin: baseAdminFields.keys({
|
|
||||||
autoComplete: joi.string(),
|
|
||||||
components: baseAdminComponentFields.keys({
|
|
||||||
Error: componentSchema,
|
|
||||||
Label: componentSchema,
|
|
||||||
afterInput: joi
|
|
||||||
.array()
|
|
||||||
.items(componentSchema)
|
|
||||||
.when('hasMany', { not: true, otherwise: joi.forbidden() }),
|
|
||||||
beforeInput: joi
|
|
||||||
.array()
|
|
||||||
.items(componentSchema)
|
|
||||||
.when('hasMany', { not: true, otherwise: joi.forbidden() }),
|
|
||||||
}),
|
|
||||||
placeholder: joi.string(),
|
|
||||||
step: joi.number(),
|
|
||||||
}),
|
|
||||||
defaultValue: joi
|
|
||||||
.alternatives()
|
|
||||||
.try(
|
|
||||||
joi.number(),
|
|
||||||
joi.func(),
|
|
||||||
joi.array().when('hasMany', { not: true, then: joi.forbidden() }),
|
|
||||||
),
|
|
||||||
hasMany: joi.boolean().default(false),
|
|
||||||
max: joi.number(),
|
|
||||||
maxRows: joi.number().when('hasMany', { is: joi.not(true), then: joi.forbidden() }),
|
|
||||||
min: joi.number(),
|
|
||||||
minRows: joi.number().when('hasMany', { is: joi.not(true), then: joi.forbidden() }),
|
|
||||||
})
|
|
||||||
|
|
||||||
export const textarea = baseField.keys({
|
|
||||||
name: joi.string().required(),
|
|
||||||
type: joi.string().valid('textarea').required(),
|
|
||||||
admin: baseAdminFields.keys({
|
|
||||||
components: baseAdminComponentFields.keys({
|
|
||||||
Error: componentSchema,
|
|
||||||
Label: componentSchema,
|
|
||||||
afterInput: joi.array().items(componentSchema),
|
|
||||||
beforeInput: joi.array().items(componentSchema),
|
|
||||||
}),
|
|
||||||
placeholder: joi.string(),
|
|
||||||
rows: joi.number(),
|
|
||||||
rtl: joi.boolean(),
|
|
||||||
}),
|
|
||||||
defaultValue: joi.alternatives().try(joi.string().allow(''), joi.func()),
|
|
||||||
maxLength: joi.number(),
|
|
||||||
minLength: joi.number(),
|
|
||||||
})
|
|
||||||
|
|
||||||
export const email = baseField.keys({
|
|
||||||
name: joi.string().required(),
|
|
||||||
type: joi.string().valid('email').required(),
|
|
||||||
admin: baseAdminFields.keys({
|
|
||||||
autoComplete: joi.string(),
|
|
||||||
components: baseAdminComponentFields.keys({
|
|
||||||
Error: componentSchema,
|
|
||||||
Label: componentSchema,
|
|
||||||
afterInput: joi.array().items(componentSchema),
|
|
||||||
beforeInput: joi.array().items(componentSchema),
|
|
||||||
}),
|
|
||||||
placeholder: joi.string(),
|
|
||||||
}),
|
|
||||||
defaultValue: joi.alternatives().try(joi.string().allow(''), joi.func()),
|
|
||||||
maxLength: joi.number(),
|
|
||||||
minLength: joi.number(),
|
|
||||||
})
|
|
||||||
|
|
||||||
export const code = baseField.keys({
|
|
||||||
name: joi.string().required(),
|
|
||||||
type: joi.string().valid('code').required(),
|
|
||||||
admin: baseAdminFields.keys({
|
|
||||||
components: baseAdminComponentFields.keys({
|
|
||||||
Error: componentSchema,
|
|
||||||
Label: componentSchema,
|
|
||||||
}),
|
|
||||||
editorOptions: joi.object().unknown(), // Editor['options'] @monaco-editor/react
|
|
||||||
language: joi.string(),
|
|
||||||
}),
|
|
||||||
defaultValue: joi.alternatives().try(joi.string().allow(''), joi.func()),
|
|
||||||
})
|
|
||||||
|
|
||||||
export const json = baseField.keys({
|
|
||||||
name: joi.string().required(),
|
|
||||||
type: joi.string().valid('json').required(),
|
|
||||||
admin: baseAdminFields.keys({
|
|
||||||
components: baseAdminComponentFields.keys({
|
|
||||||
Error: componentSchema,
|
|
||||||
Label: componentSchema,
|
|
||||||
}),
|
|
||||||
editorOptions: joi.object().unknown(), // Editor['options'] @monaco-editor/react
|
|
||||||
}),
|
|
||||||
defaultValue: joi.alternatives().try(joi.array(), joi.func(), joi.object()),
|
|
||||||
jsonSchema: joi.object().unknown(),
|
|
||||||
})
|
|
||||||
|
|
||||||
export const select = baseField.keys({
|
|
||||||
name: joi.string().required(),
|
|
||||||
type: joi.string().valid('select').required(),
|
|
||||||
admin: baseAdminFields.keys({
|
|
||||||
components: baseAdminComponentFields.keys({
|
|
||||||
Error: componentSchema,
|
|
||||||
Label: componentSchema,
|
|
||||||
}),
|
|
||||||
isClearable: joi.boolean().default(false),
|
|
||||||
isSortable: joi.boolean().default(false),
|
|
||||||
}),
|
|
||||||
dbName: joi.alternatives().try(joi.string(), joi.func()),
|
|
||||||
defaultValue: joi
|
|
||||||
.alternatives()
|
|
||||||
.try(joi.string().allow(''), joi.array().items(joi.string().allow('')), joi.func()),
|
|
||||||
enumName: joi.alternatives().try(joi.string(), joi.func()),
|
|
||||||
hasMany: joi.boolean().default(false),
|
|
||||||
options: joi
|
|
||||||
.array()
|
|
||||||
.min(1)
|
|
||||||
.items(
|
|
||||||
joi.alternatives().try(
|
|
||||||
joi.string(),
|
|
||||||
joi.object({
|
|
||||||
label: joi
|
|
||||||
.alternatives()
|
|
||||||
.try(joi.func(), joi.string(), joi.object().pattern(joi.string(), [joi.string()])),
|
|
||||||
value: joi.string().required().allow(''),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.required(),
|
|
||||||
})
|
|
||||||
|
|
||||||
export const radio = baseField.keys({
|
|
||||||
name: joi.string().required(),
|
|
||||||
type: joi.string().valid('radio').required(),
|
|
||||||
admin: baseAdminFields.keys({
|
|
||||||
components: baseAdminComponentFields.keys({
|
|
||||||
Error: componentSchema,
|
|
||||||
Label: componentSchema,
|
|
||||||
}),
|
|
||||||
layout: joi.string().valid('vertical', 'horizontal'),
|
|
||||||
}),
|
|
||||||
defaultValue: joi.alternatives().try(joi.string().allow(''), joi.func()),
|
|
||||||
enumName: joi.alternatives().try(joi.string(), joi.func()),
|
|
||||||
options: joi
|
|
||||||
.array()
|
|
||||||
.min(1)
|
|
||||||
.items(
|
|
||||||
joi.alternatives().try(
|
|
||||||
joi.string(),
|
|
||||||
joi.object({
|
|
||||||
label: joi
|
|
||||||
.alternatives()
|
|
||||||
.try(joi.func(), joi.string(), joi.object().pattern(joi.string(), [joi.string()]))
|
|
||||||
.required(),
|
|
||||||
value: joi.string().required().allow(''),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.required(),
|
|
||||||
})
|
|
||||||
|
|
||||||
export const row = baseField.keys({
|
|
||||||
type: joi.string().valid('row').required(),
|
|
||||||
admin: baseAdminFields.default(),
|
|
||||||
fields: joi.array().items(joi.link('#field')),
|
|
||||||
})
|
|
||||||
|
|
||||||
export const collapsible = baseField.keys({
|
|
||||||
type: joi.string().valid('collapsible').required(),
|
|
||||||
admin: baseAdminFields
|
|
||||||
.keys({
|
|
||||||
components: baseAdminComponentFields
|
|
||||||
.keys({
|
|
||||||
RowLabel: componentSchema.optional(),
|
|
||||||
})
|
|
||||||
.default({}),
|
|
||||||
})
|
|
||||||
.default({}),
|
|
||||||
fields: joi.array().items(joi.link('#field')),
|
|
||||||
label: joi.alternatives().conditional('admin.components.RowLabel', {
|
|
||||||
is: joi.exist(),
|
|
||||||
otherwise: joi
|
|
||||||
.alternatives()
|
|
||||||
.try(joi.string(), joi.object().pattern(joi.string(), [joi.string()]), joi.function())
|
|
||||||
.required(),
|
|
||||||
then: joi
|
|
||||||
.alternatives()
|
|
||||||
.try(joi.string(), joi.object().pattern(joi.string(), [joi.string()]), joi.function())
|
|
||||||
.optional(),
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
|
|
||||||
const tab = baseField.keys({
|
|
||||||
name: joi.string().when('localized', { is: joi.exist(), then: joi.required() }),
|
|
||||||
description: joi.alternatives().try(joi.string(), componentSchema),
|
|
||||||
fields: joi.array().items(joi.link('#field')).required(),
|
|
||||||
interfaceName: joi.string().when('name', { not: joi.exist(), then: joi.forbidden() }),
|
|
||||||
label: joi
|
|
||||||
.alternatives()
|
|
||||||
.try(joi.string(), joi.object().pattern(joi.string(), [joi.string()]))
|
|
||||||
.when('name', { is: joi.not(), then: joi.required() }),
|
|
||||||
localized: joi.boolean(),
|
|
||||||
saveToJWT: joi.alternatives().try(joi.boolean(), joi.string()),
|
|
||||||
})
|
|
||||||
|
|
||||||
export const tabs = baseField.keys({
|
|
||||||
type: joi.string().valid('tabs').required(),
|
|
||||||
admin: baseAdminFields.keys({
|
|
||||||
description: joi.forbidden(),
|
|
||||||
}),
|
|
||||||
fields: joi.forbidden(),
|
|
||||||
localized: joi.forbidden(),
|
|
||||||
tabs: joi.array().items(tab).required(),
|
|
||||||
})
|
|
||||||
|
|
||||||
export const group = baseField.keys({
|
|
||||||
name: joi.string().required(),
|
|
||||||
type: joi.string().valid('group').required(),
|
|
||||||
admin: baseAdminFields.keys({
|
|
||||||
hideGutter: joi.boolean().default(true),
|
|
||||||
}),
|
|
||||||
defaultValue: joi.alternatives().try(joi.object(), joi.func()),
|
|
||||||
fields: joi.array().items(joi.link('#field')),
|
|
||||||
interfaceName: joi.string(),
|
|
||||||
})
|
|
||||||
|
|
||||||
export const array = baseField.keys({
|
|
||||||
name: joi.string().required(),
|
|
||||||
type: joi.string().valid('array').required(),
|
|
||||||
admin: baseAdminFields
|
|
||||||
.keys({
|
|
||||||
components: baseAdminComponentFields
|
|
||||||
.keys({
|
|
||||||
RowLabel: componentSchema,
|
|
||||||
})
|
|
||||||
.default({}),
|
|
||||||
isSortable: joi.boolean(),
|
|
||||||
})
|
|
||||||
.default({}),
|
|
||||||
dbName: joi.alternatives().try(joi.string(), joi.func()),
|
|
||||||
defaultValue: joi.alternatives().try(joi.array().items(joi.object()), joi.func()),
|
|
||||||
fields: joi.array().items(joi.link('#field')).required(),
|
|
||||||
interfaceName: joi.string(),
|
|
||||||
labels: joi.object({
|
|
||||||
plural: joi
|
|
||||||
.alternatives()
|
|
||||||
.try(joi.string(), joi.object().pattern(joi.string(), [joi.string()])),
|
|
||||||
singular: joi
|
|
||||||
.alternatives()
|
|
||||||
.try(joi.string(), joi.object().pattern(joi.string(), [joi.string()])),
|
|
||||||
}),
|
|
||||||
maxRows: joi.number(),
|
|
||||||
minRows: joi.number(),
|
|
||||||
})
|
|
||||||
|
|
||||||
export const upload = baseField.keys({
|
|
||||||
name: joi.string().required(),
|
|
||||||
type: joi.string().valid('upload').required(),
|
|
||||||
admin: baseAdminFields.keys({
|
|
||||||
components: baseAdminComponentFields.keys({
|
|
||||||
Error: componentSchema,
|
|
||||||
Label: componentSchema,
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
defaultValue: joi.alternatives().try(joi.object(), joi.func()),
|
|
||||||
filterOptions: joi.alternatives().try(joi.object(), joi.func()),
|
|
||||||
maxDepth: joi.number(),
|
|
||||||
relationTo: joi.string().required(),
|
|
||||||
})
|
|
||||||
|
|
||||||
export const checkbox = baseField.keys({
|
|
||||||
name: joi.string().required(),
|
|
||||||
type: joi.string().valid('checkbox').required(),
|
|
||||||
admin: baseAdminFields.keys({
|
|
||||||
components: baseAdminComponentFields.keys({
|
|
||||||
Error: componentSchema,
|
|
||||||
Label: componentSchema,
|
|
||||||
afterInput: joi.array().items(componentSchema),
|
|
||||||
beforeInput: joi.array().items(componentSchema),
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
defaultValue: joi.alternatives().try(joi.boolean(), joi.func()),
|
|
||||||
})
|
|
||||||
|
|
||||||
export const point = baseField.keys({
|
|
||||||
name: joi.string().required(),
|
|
||||||
type: joi.string().valid('point').required(),
|
|
||||||
admin: baseAdminFields.keys({
|
|
||||||
components: baseAdminComponentFields.keys({
|
|
||||||
Error: componentSchema,
|
|
||||||
Label: componentSchema,
|
|
||||||
afterInput: joi.array().items(componentSchema),
|
|
||||||
beforeInput: joi.array().items(componentSchema),
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
defaultValue: joi.alternatives().try(joi.array().items(joi.number()).max(2).min(2), joi.func()),
|
|
||||||
})
|
|
||||||
|
|
||||||
export const relationship = baseField.keys({
|
|
||||||
name: joi.string().required(),
|
|
||||||
type: joi.string().valid('relationship').required(),
|
|
||||||
admin: baseAdminFields.keys({
|
|
||||||
allowCreate: joi.boolean().default(true),
|
|
||||||
components: baseAdminComponentFields.keys({
|
|
||||||
Error: componentSchema,
|
|
||||||
Label: componentSchema,
|
|
||||||
}),
|
|
||||||
isSortable: joi.boolean().default(false),
|
|
||||||
sortOptions: joi.alternatives().conditional(joi.ref('...relationTo'), {
|
|
||||||
is: joi.string(),
|
|
||||||
otherwise: joi.object().pattern(joi.string(), joi.string()),
|
|
||||||
then: joi.string(),
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
defaultValue: joi.alternatives().try(joi.func()),
|
|
||||||
filterOptions: joi.alternatives().try(joi.object(), joi.func()),
|
|
||||||
hasMany: joi.boolean().default(false),
|
|
||||||
max: joi
|
|
||||||
.number()
|
|
||||||
.when('hasMany', { is: joi.not(true), then: joi.forbidden() })
|
|
||||||
.warning('deprecated', { message: 'Use maxRows instead.' }),
|
|
||||||
maxDepth: joi.number(),
|
|
||||||
maxRows: joi.number().when('hasMany', { is: joi.not(true), then: joi.forbidden() }),
|
|
||||||
min: joi
|
|
||||||
.number()
|
|
||||||
.when('hasMany', { is: joi.not(true), then: joi.forbidden() })
|
|
||||||
.warning('deprecated', { message: 'Use minRows instead.' }),
|
|
||||||
minRows: joi.number().when('hasMany', { is: joi.not(true), then: joi.forbidden() }),
|
|
||||||
relationTo: joi.alternatives().try(joi.string().required(), joi.array().items(joi.string())),
|
|
||||||
})
|
|
||||||
|
|
||||||
export const blocks = baseField.keys({
|
|
||||||
name: joi.string().required(),
|
|
||||||
type: joi.string().valid('blocks').required(),
|
|
||||||
admin: baseAdminFields
|
|
||||||
.keys({
|
|
||||||
isSortable: joi.boolean(),
|
|
||||||
})
|
|
||||||
.default({}),
|
|
||||||
blocks: joi
|
|
||||||
.array()
|
|
||||||
.items(
|
|
||||||
joi.object({
|
|
||||||
slug: joi.string().required(),
|
|
||||||
admin: joi.object().keys({
|
|
||||||
components: joi.object().keys({
|
|
||||||
Label: componentSchema,
|
|
||||||
}),
|
|
||||||
custom: joi.object().pattern(joi.string(), joi.any()),
|
|
||||||
}),
|
|
||||||
custom: joi.object().pattern(joi.string(), joi.any()),
|
|
||||||
dbName: joi.alternatives().try(joi.string(), joi.func()),
|
|
||||||
fields: joi.array().items(joi.link('#field')),
|
|
||||||
graphQL: joi.object().keys({
|
|
||||||
singularName: joi.string(),
|
|
||||||
}),
|
|
||||||
imageAltText: joi.string(),
|
|
||||||
imageURL: joi.string(),
|
|
||||||
interfaceName: joi.string(),
|
|
||||||
labels: joi.object({
|
|
||||||
plural: joi
|
|
||||||
.alternatives()
|
|
||||||
.try(joi.string(), joi.object().pattern(joi.string(), [joi.string()])),
|
|
||||||
singular: joi
|
|
||||||
.alternatives()
|
|
||||||
.try(joi.string(), joi.object().pattern(joi.string(), [joi.string()])),
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.required(),
|
|
||||||
defaultValue: joi.alternatives().try(joi.array().items(joi.object()), joi.func()),
|
|
||||||
labels: joi.object({
|
|
||||||
plural: joi
|
|
||||||
.alternatives()
|
|
||||||
.try(joi.string(), joi.object().pattern(joi.string(), [joi.string()])),
|
|
||||||
singular: joi
|
|
||||||
.alternatives()
|
|
||||||
.try(joi.string(), joi.object().pattern(joi.string(), [joi.string()])),
|
|
||||||
}),
|
|
||||||
maxRows: joi.number(),
|
|
||||||
minRows: joi.number(),
|
|
||||||
})
|
|
||||||
|
|
||||||
export const richText = baseField.keys({
|
|
||||||
name: joi.string().required(),
|
|
||||||
type: joi.string().valid('richText').required(),
|
|
||||||
admin: baseAdminFields.keys({
|
|
||||||
components: baseAdminComponentFields.keys({
|
|
||||||
Error: componentSchema,
|
|
||||||
Label: componentSchema,
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
defaultValue: joi.alternatives().try(joi.array().items(joi.object()), joi.func(), joi.object()),
|
|
||||||
editor: joi
|
|
||||||
.object()
|
|
||||||
.keys({
|
|
||||||
CellComponent: componentSchema.optional(),
|
|
||||||
FieldComponent: componentSchema.optional(),
|
|
||||||
afterReadPromise: joi.func().optional(),
|
|
||||||
graphQLPopulationPromises: joi.func().optional(),
|
|
||||||
outputSchema: joi.func().optional(),
|
|
||||||
validate: joi.func().required(),
|
|
||||||
})
|
|
||||||
.unknown(),
|
|
||||||
maxDepth: joi.number(),
|
|
||||||
})
|
|
||||||
|
|
||||||
export const date = baseField.keys({
|
|
||||||
name: joi.string().required(),
|
|
||||||
type: joi.string().valid('date').required(),
|
|
||||||
admin: baseAdminFields.keys({
|
|
||||||
components: baseAdminComponentFields.keys({
|
|
||||||
Error: componentSchema,
|
|
||||||
Label: componentSchema,
|
|
||||||
afterInput: joi.array().items(componentSchema),
|
|
||||||
beforeInput: joi.array().items(componentSchema),
|
|
||||||
}),
|
|
||||||
date: joi.object({
|
|
||||||
displayFormat: joi.string(),
|
|
||||||
maxDate: joi.date(),
|
|
||||||
maxTime: joi.date(),
|
|
||||||
minDate: joi.date(),
|
|
||||||
minTime: joi.date(),
|
|
||||||
monthsToShow: joi.number(),
|
|
||||||
overrides: joi.object().unknown(),
|
|
||||||
pickerAppearance: joi.string(),
|
|
||||||
timeFormat: joi.string(),
|
|
||||||
timeIntervals: joi.number(),
|
|
||||||
}),
|
|
||||||
placeholder: joi.string(),
|
|
||||||
}),
|
|
||||||
defaultValue: joi.alternatives().try(joi.string(), joi.func()),
|
|
||||||
})
|
|
||||||
|
|
||||||
export const ui = joi.object().keys({
|
|
||||||
name: joi.string().required(),
|
|
||||||
type: joi.string().valid('ui').required(),
|
|
||||||
admin: joi
|
|
||||||
.object()
|
|
||||||
.keys({
|
|
||||||
components: joi
|
|
||||||
.object()
|
|
||||||
.keys({
|
|
||||||
Cell: componentSchema,
|
|
||||||
Field: componentSchema,
|
|
||||||
})
|
|
||||||
.default({}),
|
|
||||||
condition: joi.func(),
|
|
||||||
custom: joi.object().pattern(joi.string(), joi.any()),
|
|
||||||
disableListColumn: joi.boolean().default(false),
|
|
||||||
position: joi.string().valid('sidebar'),
|
|
||||||
width: joi.string(),
|
|
||||||
})
|
|
||||||
.default(),
|
|
||||||
custom: joi.object().pattern(joi.string(), joi.any()),
|
|
||||||
label: joi.alternatives().try(joi.string(), joi.object().pattern(joi.string(), [joi.string()])),
|
|
||||||
})
|
|
||||||
|
|
||||||
const fieldSchema = joi
|
|
||||||
.alternatives()
|
|
||||||
.try(
|
|
||||||
text,
|
|
||||||
number,
|
|
||||||
textarea,
|
|
||||||
email,
|
|
||||||
code,
|
|
||||||
json,
|
|
||||||
select,
|
|
||||||
group,
|
|
||||||
array,
|
|
||||||
row,
|
|
||||||
collapsible,
|
|
||||||
tabs,
|
|
||||||
radio,
|
|
||||||
relationship,
|
|
||||||
checkbox,
|
|
||||||
upload,
|
|
||||||
richText,
|
|
||||||
blocks,
|
|
||||||
date,
|
|
||||||
point,
|
|
||||||
ui,
|
|
||||||
)
|
|
||||||
.id('field')
|
|
||||||
|
|
||||||
export default fieldSchema
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
import joi from 'joi'
|
|
||||||
|
|
||||||
import { endpointsSchema } from '../../config/schema.js'
|
|
||||||
import {
|
|
||||||
componentSchema,
|
|
||||||
customViewSchema,
|
|
||||||
livePreviewSchema,
|
|
||||||
} from '../../config/shared/componentSchema.js'
|
|
||||||
import { openGraphSchema } from '../../config/shared/openGraphSchema.js'
|
|
||||||
|
|
||||||
const globalSchema = joi
|
|
||||||
.object()
|
|
||||||
.keys({
|
|
||||||
slug: joi.string().required(),
|
|
||||||
access: joi.object({
|
|
||||||
read: joi.func(),
|
|
||||||
readVersions: joi.func(),
|
|
||||||
update: joi.func(),
|
|
||||||
}),
|
|
||||||
admin: joi.object({
|
|
||||||
components: joi.object({
|
|
||||||
elements: joi.object({
|
|
||||||
Description: componentSchema,
|
|
||||||
PreviewButton: componentSchema,
|
|
||||||
PublishButton: componentSchema,
|
|
||||||
SaveButton: componentSchema,
|
|
||||||
SaveDraftButton: componentSchema,
|
|
||||||
}),
|
|
||||||
views: joi.object({
|
|
||||||
Edit: joi.alternatives().try(
|
|
||||||
componentSchema,
|
|
||||||
joi.object({
|
|
||||||
API: joi.alternatives().try(componentSchema, customViewSchema),
|
|
||||||
Default: joi.alternatives().try(componentSchema, customViewSchema),
|
|
||||||
Preview: joi.alternatives().try(componentSchema, customViewSchema),
|
|
||||||
Version: joi.alternatives().try(componentSchema, customViewSchema),
|
|
||||||
Versions: joi.alternatives().try(componentSchema, customViewSchema),
|
|
||||||
// Relationships
|
|
||||||
// References
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
custom: joi.object().pattern(joi.string(), joi.any()),
|
|
||||||
description: joi
|
|
||||||
.alternatives()
|
|
||||||
.try(joi.func(), joi.object().pattern(joi.string(), [joi.string()]), joi.string()),
|
|
||||||
group: joi
|
|
||||||
.alternatives()
|
|
||||||
.try(joi.string(), joi.object().pattern(joi.string(), [joi.string()])),
|
|
||||||
hidden: joi.alternatives().try(joi.boolean(), joi.func()),
|
|
||||||
hideAPIURL: joi.boolean(),
|
|
||||||
livePreview: joi.object(livePreviewSchema),
|
|
||||||
meta: joi.object({
|
|
||||||
description: joi.string(),
|
|
||||||
openGraph: openGraphSchema,
|
|
||||||
}),
|
|
||||||
preview: joi.func(),
|
|
||||||
}),
|
|
||||||
custom: joi.object().pattern(joi.string(), joi.any()),
|
|
||||||
dbName: joi.alternatives().try(joi.string(), joi.func()),
|
|
||||||
endpoints: endpointsSchema,
|
|
||||||
fields: joi.array(),
|
|
||||||
graphQL: joi.alternatives().try(
|
|
||||||
joi.object().keys({
|
|
||||||
name: joi.string(),
|
|
||||||
}),
|
|
||||||
joi.boolean(),
|
|
||||||
),
|
|
||||||
hooks: joi.object({
|
|
||||||
afterChange: joi.array().items(joi.func()),
|
|
||||||
afterRead: joi.array().items(joi.func()),
|
|
||||||
beforeChange: joi.array().items(joi.func()),
|
|
||||||
beforeRead: joi.array().items(joi.func()),
|
|
||||||
beforeValidate: joi.array().items(joi.func()),
|
|
||||||
}),
|
|
||||||
label: joi
|
|
||||||
.alternatives()
|
|
||||||
.try(joi.func(), joi.string(), joi.object().pattern(joi.string(), [joi.string()])),
|
|
||||||
typescript: joi.object().keys({
|
|
||||||
interface: joi.string(),
|
|
||||||
}),
|
|
||||||
versions: joi.alternatives().try(
|
|
||||||
joi.object({
|
|
||||||
drafts: joi.alternatives().try(
|
|
||||||
joi.object({
|
|
||||||
autosave: joi.alternatives().try(
|
|
||||||
joi.boolean(),
|
|
||||||
joi.object({
|
|
||||||
interval: joi.number(),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
validate: joi.boolean(),
|
|
||||||
}),
|
|
||||||
joi.boolean(),
|
|
||||||
),
|
|
||||||
max: joi.number(),
|
|
||||||
}),
|
|
||||||
joi.boolean(),
|
|
||||||
),
|
|
||||||
})
|
|
||||||
.unknown()
|
|
||||||
|
|
||||||
export default globalSchema
|
|
||||||
@@ -57,7 +57,6 @@ import { decrypt, encrypt } from './auth/crypto.js'
|
|||||||
import { APIKeyAuthentication } from './auth/strategies/apiKey.js'
|
import { APIKeyAuthentication } from './auth/strategies/apiKey.js'
|
||||||
import { JWTAuthentication } from './auth/strategies/jwt.js'
|
import { JWTAuthentication } from './auth/strategies/jwt.js'
|
||||||
import localOperations from './collections/operations/local/index.js'
|
import localOperations from './collections/operations/local/index.js'
|
||||||
import { validateSchema } from './config/validate.js'
|
|
||||||
import { consoleEmailAdapter } from './email/consoleEmailAdapter.js'
|
import { consoleEmailAdapter } from './email/consoleEmailAdapter.js'
|
||||||
import { fieldAffectsData } from './fields/config/types.js'
|
import { fieldAffectsData } from './fields/config/types.js'
|
||||||
import localGlobalOperations from './globals/operations/local/index.js'
|
import localGlobalOperations from './globals/operations/local/index.js'
|
||||||
@@ -488,10 +487,6 @@ export class BasePayload {
|
|||||||
|
|
||||||
this.config = await options.config
|
this.config = await options.config
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
|
||||||
validateSchema(this.config, this.logger)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.config.secret) {
|
if (!this.config.secret) {
|
||||||
throw new Error('Error: missing secret key. A secret key is needed to secure Payload.')
|
throw new Error('Error: missing secret key. A secret key is needed to secure Payload.')
|
||||||
}
|
}
|
||||||
|
|||||||
44
pnpm-lock.yaml
generated
44
pnpm-lock.yaml
generated
@@ -694,9 +694,6 @@ importers:
|
|||||||
image-size:
|
image-size:
|
||||||
specifier: ^1.1.1
|
specifier: ^1.1.1
|
||||||
version: 1.1.1
|
version: 1.1.1
|
||||||
joi:
|
|
||||||
specifier: ^17.12.1
|
|
||||||
version: 17.13.3
|
|
||||||
json-schema-to-typescript:
|
json-schema-to-typescript:
|
||||||
specifier: 11.0.3
|
specifier: 11.0.3
|
||||||
version: 11.0.3
|
version: 11.0.3
|
||||||
@@ -743,9 +740,6 @@ importers:
|
|||||||
'@types/express-fileupload':
|
'@types/express-fileupload':
|
||||||
specifier: 1.4.1
|
specifier: 1.4.1
|
||||||
version: 1.4.1
|
version: 1.4.1
|
||||||
'@types/joi':
|
|
||||||
specifier: 14.3.4
|
|
||||||
version: 14.3.4
|
|
||||||
'@types/json-schema':
|
'@types/json-schema':
|
||||||
specifier: 7.0.15
|
specifier: 7.0.15
|
||||||
version: 7.0.15
|
version: 7.0.15
|
||||||
@@ -5107,16 +5101,6 @@ packages:
|
|||||||
- encoding
|
- encoding
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
/@hapi/hoek@9.3.0:
|
|
||||||
resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@hapi/topo@5.1.0:
|
|
||||||
resolution: {integrity: sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==}
|
|
||||||
dependencies:
|
|
||||||
'@hapi/hoek': 9.3.0
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@humanwhocodes/module-importer@1.0.1:
|
/@humanwhocodes/module-importer@1.0.1:
|
||||||
resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
|
resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
|
||||||
engines: {node: '>=12.22'}
|
engines: {node: '>=12.22'}
|
||||||
@@ -6174,20 +6158,6 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@sentry/types': 7.118.0
|
'@sentry/types': 7.118.0
|
||||||
|
|
||||||
/@sideway/address@4.1.5:
|
|
||||||
resolution: {integrity: sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==}
|
|
||||||
dependencies:
|
|
||||||
'@hapi/hoek': 9.3.0
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@sideway/formula@3.0.1:
|
|
||||||
resolution: {integrity: sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@sideway/pinpoint@2.0.0:
|
|
||||||
resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@sinclair/typebox@0.27.8:
|
/@sinclair/typebox@0.27.8:
|
||||||
resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
|
resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
|
||||||
|
|
||||||
@@ -7011,10 +6981,6 @@ packages:
|
|||||||
pretty-format: 29.7.0
|
pretty-format: 29.7.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@types/joi@14.3.4:
|
|
||||||
resolution: {integrity: sha512-1TQNDJvIKlgYXGNIABfgFp9y0FziDpuGrd799Q5RcnsDu+krD+eeW/0Fs5PHARvWWFelOhIG2OPCo6KbadBM4A==}
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@types/jsdom@20.0.1:
|
/@types/jsdom@20.0.1:
|
||||||
resolution: {integrity: sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==}
|
resolution: {integrity: sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -12057,16 +12023,6 @@ packages:
|
|||||||
hasBin: true
|
hasBin: true
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/joi@17.13.3:
|
|
||||||
resolution: {integrity: sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==}
|
|
||||||
dependencies:
|
|
||||||
'@hapi/hoek': 9.3.0
|
|
||||||
'@hapi/topo': 5.1.0
|
|
||||||
'@sideway/address': 4.1.5
|
|
||||||
'@sideway/formula': 3.0.1
|
|
||||||
'@sideway/pinpoint': 2.0.0
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/joycon@3.1.1:
|
/joycon@3.1.1:
|
||||||
resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
|
resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|||||||
Reference in New Issue
Block a user