2018-08-07 10:27:15 -06:00
|
|
|
import AJV from 'ajv';
|
|
|
|
import ajvErrors from 'ajv-errors';
|
2018-08-07 14:46:54 -06:00
|
|
|
import { formatExtensions, frontmatterFormats, extensionFormatters } from 'Formats/formats';
|
|
|
|
import { IDENTIFIER_FIELDS } from 'Constants/fieldInference';
|
2018-08-07 10:27:15 -06:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Config for fields in both file and folder collections.
|
|
|
|
*/
|
|
|
|
const fieldsConfig = {
|
2018-08-07 14:46:54 -06:00
|
|
|
type: 'array',
|
2018-08-07 10:27:15 -06:00
|
|
|
minItems: 1,
|
|
|
|
items: {
|
|
|
|
// ------- Each field: -------
|
2018-08-07 14:46:54 -06:00
|
|
|
type: 'object',
|
2018-08-07 10:27:15 -06:00
|
|
|
properties: {
|
2018-08-07 14:46:54 -06:00
|
|
|
name: { type: 'string' },
|
|
|
|
label: { type: 'string' },
|
|
|
|
widget: { type: 'string' },
|
|
|
|
required: { type: 'boolean' },
|
2018-08-07 10:27:15 -06:00
|
|
|
},
|
2018-08-07 14:46:54 -06:00
|
|
|
required: ['name'],
|
2018-08-07 10:27:15 -06:00
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The schema had to be wrapped in a function to
|
|
|
|
* fix a circular dependency problem for WebPack,
|
|
|
|
* where the imports get resolved asyncronously.
|
|
|
|
*/
|
|
|
|
const getConfigSchema = () => ({
|
2018-08-07 14:46:54 -06:00
|
|
|
type: 'object',
|
2018-08-07 10:27:15 -06:00
|
|
|
properties: {
|
|
|
|
backend: {
|
2018-08-07 14:46:54 -06:00
|
|
|
type: 'object',
|
|
|
|
properties: { name: { type: 'string', examples: ['test-repo'] } },
|
|
|
|
required: ['name'],
|
2018-08-07 10:27:15 -06:00
|
|
|
},
|
2018-08-07 14:46:54 -06:00
|
|
|
display_url: { type: 'string', examples: ['https://example.com'] },
|
|
|
|
media_folder: { type: 'string', examples: ['assets/uploads'] },
|
|
|
|
public_folder: { type: 'string', examples: ['/uploads'] },
|
2018-08-30 16:24:28 -04:00
|
|
|
media_library: {
|
|
|
|
type: 'object',
|
|
|
|
properties: {
|
|
|
|
name: { type: 'string', examples: ['uploadcare'] },
|
|
|
|
config: { type: 'object' },
|
|
|
|
},
|
|
|
|
required: ['name'],
|
|
|
|
},
|
2018-08-07 10:27:15 -06:00
|
|
|
publish_mode: {
|
2018-08-07 14:46:54 -06:00
|
|
|
type: 'string',
|
|
|
|
enum: ['editorial_workflow'],
|
|
|
|
examples: ['editorial_workflow'],
|
2018-08-07 10:27:15 -06:00
|
|
|
},
|
|
|
|
slug: {
|
2018-08-07 14:46:54 -06:00
|
|
|
type: 'object',
|
2018-08-07 10:27:15 -06:00
|
|
|
properties: {
|
2018-08-07 14:46:54 -06:00
|
|
|
encoding: { type: 'string', enum: ['unicode', 'ascii'] },
|
|
|
|
clean_accents: { type: 'boolean' },
|
2018-08-07 10:27:15 -06:00
|
|
|
},
|
|
|
|
},
|
|
|
|
collections: {
|
2018-08-07 14:46:54 -06:00
|
|
|
type: 'array',
|
2018-08-07 10:27:15 -06:00
|
|
|
minItems: 1,
|
|
|
|
items: {
|
|
|
|
// ------- Each collection: -------
|
2018-08-07 14:46:54 -06:00
|
|
|
type: 'object',
|
2018-08-07 10:27:15 -06:00
|
|
|
properties: {
|
2018-08-07 14:46:54 -06:00
|
|
|
name: { type: 'string' },
|
|
|
|
label: { type: 'string' },
|
|
|
|
label_singular: { type: 'string' },
|
|
|
|
description: { type: 'string' },
|
|
|
|
folder: { type: 'string' },
|
2018-08-07 10:27:15 -06:00
|
|
|
files: {
|
2018-08-07 14:46:54 -06:00
|
|
|
type: 'array',
|
2018-08-07 10:27:15 -06:00
|
|
|
items: {
|
|
|
|
// ------- Each file: -------
|
2018-08-07 14:46:54 -06:00
|
|
|
type: 'object',
|
2018-08-07 10:27:15 -06:00
|
|
|
properties: {
|
2018-08-07 14:46:54 -06:00
|
|
|
name: { type: 'string' },
|
|
|
|
label: { type: 'string' },
|
|
|
|
label_singular: { type: 'string' },
|
|
|
|
description: { type: 'string' },
|
|
|
|
file: { type: 'string' },
|
2018-08-07 10:27:15 -06:00
|
|
|
fields: fieldsConfig,
|
|
|
|
},
|
2018-08-07 14:46:54 -06:00
|
|
|
required: ['name', 'label', 'file', 'fields'],
|
2018-08-07 10:27:15 -06:00
|
|
|
},
|
|
|
|
},
|
2018-08-07 14:46:54 -06:00
|
|
|
slug: { type: 'string' },
|
|
|
|
create: { type: 'boolean' },
|
2018-08-07 10:27:15 -06:00
|
|
|
editor: {
|
2018-08-07 14:46:54 -06:00
|
|
|
type: 'object',
|
2018-08-07 10:27:15 -06:00
|
|
|
properties: {
|
2018-08-07 14:46:54 -06:00
|
|
|
preview: { type: 'boolean' },
|
2018-08-07 10:27:15 -06:00
|
|
|
},
|
|
|
|
},
|
2018-08-07 14:46:54 -06:00
|
|
|
format: { type: 'string', enum: Object.keys(formatExtensions) },
|
|
|
|
extension: { type: 'string' },
|
|
|
|
frontmatter_delimiter: { type: 'string' },
|
2018-08-07 10:27:15 -06:00
|
|
|
fields: fieldsConfig,
|
|
|
|
},
|
2018-08-07 14:46:54 -06:00
|
|
|
required: ['name', 'label'],
|
|
|
|
oneOf: [{ required: ['files'] }, { required: ['folder', 'fields'] }],
|
|
|
|
if: { required: ['extension'] },
|
2018-08-07 10:27:15 -06:00
|
|
|
then: {
|
|
|
|
// Cannot infer format from extension.
|
|
|
|
if: {
|
|
|
|
properties: {
|
|
|
|
extension: { enum: Object.keys(extensionFormatters) },
|
|
|
|
},
|
|
|
|
},
|
2018-08-07 14:46:54 -06:00
|
|
|
else: { required: ['format'] },
|
2018-08-07 10:27:15 -06:00
|
|
|
},
|
|
|
|
dependencies: {
|
|
|
|
frontmatter_delimiter: {
|
|
|
|
properties: {
|
|
|
|
format: { enum: frontmatterFormats },
|
|
|
|
},
|
2018-08-07 14:46:54 -06:00
|
|
|
required: ['format'],
|
2018-08-07 10:27:15 -06:00
|
|
|
},
|
|
|
|
folder: {
|
|
|
|
errorMessage: {
|
|
|
|
_: 'must have a field that is a valid entry identifier',
|
|
|
|
},
|
|
|
|
properties: {
|
|
|
|
fields: {
|
|
|
|
contains: {
|
|
|
|
properties: {
|
|
|
|
name: { enum: IDENTIFIER_FIELDS },
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2018-08-30 16:24:28 -04:00
|
|
|
required: ['backend', 'collections'],
|
|
|
|
anyOf: [{ required: ['media_folder'] }, { required: ['media_library'] }],
|
2018-08-07 10:27:15 -06:00
|
|
|
});
|
|
|
|
|
|
|
|
class ConfigError extends Error {
|
|
|
|
constructor(errors, ...args) {
|
|
|
|
const message = errors
|
|
|
|
.map(({ message, dataPath }) => {
|
|
|
|
const dotPath = dataPath
|
|
|
|
.slice(1)
|
2018-08-07 14:46:54 -06:00
|
|
|
.split('/')
|
2018-08-07 10:27:15 -06:00
|
|
|
.map(seg => (seg.match(/^\d+$/) ? `[${seg}]` : `.${seg}`))
|
2018-08-07 14:46:54 -06:00
|
|
|
.join('')
|
2018-08-07 10:27:15 -06:00
|
|
|
.slice(1);
|
2018-08-07 14:46:54 -06:00
|
|
|
return `${dotPath ? `'${dotPath}'` : 'config'} ${message}`;
|
2018-08-07 10:27:15 -06:00
|
|
|
})
|
2018-08-07 14:46:54 -06:00
|
|
|
.join('\n');
|
2018-08-07 10:27:15 -06:00
|
|
|
super(message, ...args);
|
|
|
|
|
|
|
|
this.errors = errors;
|
|
|
|
this.message = message;
|
|
|
|
}
|
|
|
|
|
|
|
|
toString() {
|
|
|
|
return this.message;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* `validateConfig` is a pure function. It does not mutate
|
2018-08-07 14:46:54 -06:00
|
|
|
* the config that is passed in.
|
2018-08-07 10:27:15 -06:00
|
|
|
*/
|
|
|
|
export function validateConfig(config) {
|
|
|
|
const ajv = new AJV({ allErrors: true, jsonPointers: true });
|
|
|
|
ajvErrors(ajv);
|
|
|
|
|
|
|
|
const valid = ajv.validate(getConfigSchema(), config);
|
|
|
|
if (!valid) {
|
|
|
|
console.error('Config Errors', ajv.errors);
|
|
|
|
throw new ConfigError(ajv.errors);
|
|
|
|
}
|
|
|
|
}
|