diff --git a/demo/payload.config.js b/demo/payload.config.js index 2b7b07425a..14442d9d12 100644 --- a/demo/payload.config.js +++ b/demo/payload.config.js @@ -33,11 +33,11 @@ module.exports = buildConfig({ admin: { user: 'admins', // indexHTML: 'custom-index.html', - meta: { - titleSuffix: '- Payload Demo', - // ogImage: '/static/find-image-here.jpg', - // favicon: '/img/whatever.png', - }, + // meta: { + // titleSuffix: '- Payload Demo', + // // ogImage: '/static/find-image-here.jpg', + // // favicon: '/img/whatever.png', + // }, disable: false, components: { // Nav: () => ( diff --git a/package.json b/package.json index e5f49a5b1b..7fb4db9217 100644 --- a/package.json +++ b/package.json @@ -75,6 +75,7 @@ "is-hotkey": "^0.1.6", "isomorphic-fetch": "^2.2.1", "jest": "26.6.3", + "joi": "^17.3.0", "jsonwebtoken": "^8.5.1", "jwt-decode": "^3.0.0", "method-override": "^3.0.0", diff --git a/src/collections/config/schema.json b/src/collections/config/schema.json deleted file mode 100644 index 3c60c86564..0000000000 --- a/src/collections/config/schema.json +++ /dev/null @@ -1,154 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "collection.schema.json", - "title": "Payload Collection Configuration", - "type": "object", - "required": [ - "slug", - "fields" - ], - "properties": { - "slug": { - "type": "string" - }, - "labels": { - "type": "object", - "properties": { - "singular": { - "type": "string", - "description": "Name used when referring to an instance of the collection" - }, - "plural": { - "type": "string", - "description": "Name used when referring to set of instances for the collection" - } - } - }, - "access": { - "type": "object", - "description": "Callable functions to determine permission access" - }, - "auth": { - "type": ["object", "boolean"], - "description": "Authentication properties", - "properties": { - "verify": { - "description": "Require users to verify their email before being able to login for this collection", - "anyOf": [ - { - "type": "boolean", - "description": "Enable email verification of user accounts, set to true to enable the feature using Payload default settings" - }, - { - "type": "object", - "anyOf": [ - { - "required": [ - "generateEmailSubject" - ], - "properties": { - "generateEmailSubject": { - "type": "string", - "description": "Subject field used when sending verify email for new user accounts" - } - } - }, - { - "required": [ - "generateEmailHTML" - ], - "generateEmailHTML": { - "type": "string", - "description": "Function that returns HTML for the body of the email sent to verify user accounts" - } - } - ] - } - ] - } - } - }, - "fields": { - "type": "array", - "description": "The attributes of the collection", - "items": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "The attribute key of the field used on model instances" - }, - "type": { - "type": "string", - "description": "" - }, - "label": { - "type": "string", - "description": "Field label for the admin panel" - }, - "required": { - "type": "boolean", - "description": "Enforce attribute has a value when saving" - }, - "defaultValue": { - "description": "When not present, will be saved with when not given" - }, - "unique": { - "description": "Enforce instances in the collection to not repeat the value", - "type": "boolean" - }, - "access": { - "description": "Field level permissions", - "type": "object", - "properties": { - "create": {}, - "update": {}, - "read": {} - } - }, - "relationTo": { - "description": "The name of the collection being referenced", - "anyOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } - ] - }, - "options": { - "description": "Choices", - "type": "array", - "items": { - "anyOf": [ - {"type": "string"}, - { - "type": "object", - "properties": { - "value": { - "description": "The data stored and returned for the selection", - "type": "string" - }, - "label": { - "description": "The name that identifies the value to be selected", - "type": "string" - } - } - } - ] - } - } - } - } - }, - "timestamps": { - "type": "boolean", - "description": "Adds createdAt and updatedAt fields for the collection", - "default": true - } - } -} diff --git a/src/collections/config/schema.ts b/src/collections/config/schema.ts new file mode 100644 index 0000000000..0bc9f60af1 --- /dev/null +++ b/src/collections/config/schema.ts @@ -0,0 +1,7 @@ +import Joi from 'joi'; + +const schema = Joi.object().keys({ + slug: Joi.string().required(), +}).unknown(); + +export default schema; diff --git a/src/config/sanitize.ts b/src/config/sanitize.ts index 0557702a99..68ee66feaf 100644 --- a/src/config/sanitize.ts +++ b/src/config/sanitize.ts @@ -11,8 +11,6 @@ const sanitizeConfig = (config: Config): Config => { // TODO: remove default values from sanitize in favor of assigning in the schema within validateSchema and use https://www.npmjs.com/package/ajv#coercing-data-types where needed if (sanitizedConfig.publicENV === undefined) sanitizedConfig.publicENV = {}; - if (sanitizedConfig.defaultDepth === undefined) sanitizedConfig.defaultDepth = 2; - sanitizedConfig.collections = sanitizedConfig.collections.map((collection) => sanitizeCollection(sanitizedConfig.collections, collection)); checkDuplicateCollections(sanitizedConfig.collections); diff --git a/src/config/schema.json b/src/config/schema.json deleted file mode 100644 index 458fd0d319..0000000000 --- a/src/config/schema.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "payload.schema.json", - "title": "Payload Configuration", - "type": "object", - "required": [ - "collections" - ], - "properties": { - "admin": { - "type": "object", - "description": "Admin panel configuration", - "properties": { - "user": { - "type": "string", - "description": "Collection name users designated to access the panel" - }, - "meta": { - "type": "object", - "description": "Admin web interface configuration", - "required": [ - "titleSuffix" - ], - "properties": { - "titleSuffix": { - "type": "string", - "description": "Text to add at the end of the page title of the admin html head" - }, - "ogImage": { - "type": "string", - "description": "src url for the admin image" - }, - "favicon": { - "type": "string", - "description": "Admin panel favicon file to use" - } - } - } - } - }, - "maxDepth": { - "type": "number", - "description": "The maximum population depth allowed for relationship fields", - "default": 10 - }, - "collections": { - "type": "array", - "items": { - "$ref": "./collection.schema.json" - } - } - } -} diff --git a/src/config/schema.ts b/src/config/schema.ts new file mode 100644 index 0000000000..1f3c48c35a --- /dev/null +++ b/src/config/schema.ts @@ -0,0 +1,53 @@ +import Joi from 'joi'; +import collectionSchema from '../collections/config/schema'; +import globalSchema from '../globals/config/schema'; + +const schema = Joi.object().keys({ + serverURL: Joi.string().required(), + routes: Joi.object() + .keys({ + admin: Joi.string() + .default('/admin'), + api: Joi.string() + .default('/api'), + graphQL: Joi.string() + .default('/graphql'), + graphQLPlayground: Joi.string() + .default('/graphql-playground'), + }).default(), + collections: Joi.array() + .items(collectionSchema) + .default([]), + globals: Joi.array() + .items(globalSchema) + .default([]), + admin: Joi.object() + .keys({ + user: Joi.string() + .default('users'), + meta: Joi.object() + .keys({ + titleSuffix: Joi.string() + .default('- Payload'), + ogImage: Joi.string() + .default('/static/img/find-image-here.jpg'), + favicon: Joi.string() + .default('/static/img/whatever.png'), + }) + .default(), + disable: Joi.bool() + .default(false), + components: Joi.object() + .keys({}), + }).default(), + defaultDepth: Joi.number() + .min(0) + .max(30) + .default(3), + maxDepth: Joi.number() + .min(0) + .max(100) + .default(11), +}).unknown(); + +export default schema; diff --git a/src/config/validate.ts b/src/config/validate.ts index ce1d755bd1..460c645530 100644 --- a/src/config/validate.ts +++ b/src/config/validate.ts @@ -1,20 +1,22 @@ -import Ajv from 'ajv'; -import * as configSchema from './schema.json'; -import * as collectionSchema from '../collections/config/schema.json'; - -import InvalidSchema from '../errors/InvalidSchema'; +import schema from './schema'; import { PayloadConfig, Config } from './types'; const validateSchema = (config: PayloadConfig): Config => { - const ajv = new Ajv({ useDefaults: true }); - const validate = ajv.addSchema(collectionSchema, '../collections/config/schema.json').compile(configSchema); - const valid = validate(config); + const result = schema.validate(config, { + abortEarly: false, + }); - if (!valid) { - throw new InvalidSchema(`Invalid payload config provided. Found ${validate.errors.length} errors`, validate.errors); + if (result.error) { + console.error(`There were ${result.error.details.length} errors validating your Payload config`); + + result.error.details.forEach(({ message }, i) => { + console.error(`${i + 1}: ${message}`); + }); + + process.exit(1); } - return config as Config; + return result.value as Config; }; export default validateSchema; diff --git a/src/globals/config/schema.ts b/src/globals/config/schema.ts new file mode 100644 index 0000000000..0bc9f60af1 --- /dev/null +++ b/src/globals/config/schema.ts @@ -0,0 +1,7 @@ +import Joi from 'joi'; + +const schema = Joi.object().keys({ + slug: Joi.string().required(), +}).unknown(); + +export default schema; diff --git a/yarn.lock b/yarn.lock index e67742e363..674dbe7385 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1140,6 +1140,18 @@ resolved "https://registry.yarnpkg.com/@hapi/bourne/-/bourne-2.0.0.tgz#5bb2193eb685c0007540ca61d166d4e1edaf918d" integrity sha512-WEezM1FWztfbzqIUbsDzFRVMxSoLy3HugVcux6KDDtTqzPsLE8NDRHfXvev66aH1i2oOKKar3/XDjbvh/OUBdg== +"@hapi/hoek@^9.0.0": + version "9.1.0" + resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.1.0.tgz#6c9eafc78c1529248f8f4d92b0799a712b6052c6" + integrity sha512-i9YbZPN3QgfighY/1X1Pu118VUz2Fmmhd6b2n0/O8YVgGGfw0FbUYoA97k7FkpGJ+pLCFEDLUmAPPV4D1kpeFw== + +"@hapi/topo@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.0.0.tgz#c19af8577fa393a06e9c77b60995af959be721e7" + integrity sha512-tFJlT47db0kMqVm3H4nQYgn6Pwg10GTZHb1pwmSiv1K4ks6drQOtfEF5ZnPjkvC+y4/bUPHK+bc87QvLcL+WMw== + dependencies: + "@hapi/hoek" "^9.0.0" + "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" @@ -1370,6 +1382,23 @@ resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.5.4.tgz#de25b5da9f727985a3757fd59b5d028aba75841a" integrity sha512-ZpKr+WTb8zsajqgDkvCEWgp6d5eJT6Q63Ng2neTbzBO76Lbe91vX/iVIW9dikq+Fs3yEo+ls4cxeXABD2LtcbQ== +"@sideway/address@^4.1.0": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.0.tgz#0b301ada10ac4e0e3fa525c90615e0b61a72b78d" + integrity sha512-wAH/JYRXeIFQRsxerIuLjgUu2Xszam+O5xKeatJ4oudShOOirfmsQ1D6LL54XOU2tizpCYku+s1wmU0SYdpoSA== + dependencies: + "@hapi/hoek" "^9.0.0" + +"@sideway/formula@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.0.tgz#fe158aee32e6bd5de85044be615bc08478a0a13c" + integrity sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg== + +"@sideway/pinpoint@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df" + integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ== + "@sinonjs/commons@^1.7.0": version "1.8.1" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.1.tgz#e7df00f98a203324f6dc7cc606cad9d4a8ab2217" @@ -7122,6 +7151,17 @@ jmespath@^0.15.0: resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.15.0.tgz#a3f222a9aae9f966f5d27c796510e28091764217" integrity sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc= +joi@^17.3.0: + version "17.3.0" + resolved "https://registry.yarnpkg.com/joi/-/joi-17.3.0.tgz#f1be4a6ce29bc1716665819ac361dfa139fff5d2" + integrity sha512-Qh5gdU6niuYbUIUV5ejbsMiiFmBdw8Kcp8Buj2JntszCkCfxJ9Cz76OtHxOZMPXrt5810iDIXs+n1nNVoquHgg== + dependencies: + "@hapi/hoek" "^9.0.0" + "@hapi/topo" "^5.0.0" + "@sideway/address" "^4.1.0" + "@sideway/formula" "^3.0.0" + "@sideway/pinpoint" "^2.0.0" + joycon@^2.2.5: version "2.2.5" resolved "https://registry.yarnpkg.com/joycon/-/joycon-2.2.5.tgz#8d4cf4cbb2544d7b7583c216fcdfec19f6be1615"