improvement(validation): use config schema definition for validation (#1363)
This commit is contained in:
parent
7bcdb2053d
commit
8cc6dc78ec
@ -19,6 +19,8 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ajv": "^6.4.0",
|
||||
"ajv-errors": "^1.0.0",
|
||||
"create-react-class": "^15.6.0",
|
||||
"diacritics": "^1.3.0",
|
||||
"emotion": "^9.2.6",
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { fromJS } from 'immutable';
|
||||
import { applyDefaults, validateConfig } from '../config';
|
||||
import { applyDefaults } from '../config';
|
||||
|
||||
describe('config', () => {
|
||||
describe('applyDefaults', () => {
|
||||
@ -55,77 +55,4 @@ describe('config', () => {
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateConfig', () => {
|
||||
it('should return the config if no errors', () => {
|
||||
const collections = [{
|
||||
name: 'posts',
|
||||
folder: '_posts',
|
||||
fields: [{ name: 'title', label: 'title' }],
|
||||
}];
|
||||
const config = fromJS({
|
||||
foo: 'bar',
|
||||
backend: { name: 'bar' },
|
||||
media_folder: 'baz',
|
||||
collections,
|
||||
});
|
||||
expect(
|
||||
validateConfig(config)
|
||||
).toEqual(config);
|
||||
});
|
||||
|
||||
it('should throw if backend is not defined in config', () => {
|
||||
expect(() => {
|
||||
validateConfig(fromJS({ foo: 'bar' }));
|
||||
}).toThrowError('Error in configuration file: A `backend` wasn\'t found. Check your config.yml file.');
|
||||
});
|
||||
|
||||
it('should throw if backend name is not defined in config', () => {
|
||||
expect(() => {
|
||||
validateConfig(fromJS({ foo: 'bar', backend: {} }));
|
||||
}).toThrowError('Error in configuration file: A `backend.name` wasn\'t found. Check your config.yml file.');
|
||||
});
|
||||
|
||||
it('should throw if backend name is not a string in config', () => {
|
||||
expect(() => {
|
||||
validateConfig(fromJS({ foo: 'bar', backend: { name: { } } }));
|
||||
}).toThrowError('Error in configuration file: Your `backend.name` must be a string. Check your config.yml file.');
|
||||
});
|
||||
|
||||
it('should throw if media_folder is not defined in config', () => {
|
||||
expect(() => {
|
||||
validateConfig(fromJS({ foo: 'bar', backend: { name: 'bar' } }));
|
||||
}).toThrowError('Error in configuration file: A `media_folder` wasn\'t found. Check your config.yml file.');
|
||||
});
|
||||
|
||||
it('should throw if media_folder is not a string in config', () => {
|
||||
expect(() => {
|
||||
validateConfig(fromJS({ foo: 'bar', backend: { name: 'bar' }, media_folder: {} }));
|
||||
}).toThrowError('Error in configuration file: Your `media_folder` must be a string. Check your config.yml file.');
|
||||
});
|
||||
|
||||
it('should throw if collections is not defined in config', () => {
|
||||
expect(() => {
|
||||
validateConfig(fromJS({ foo: 'bar', backend: { name: 'bar' }, media_folder: 'baz' }));
|
||||
}).toThrowError('Error in configuration file: A `collections` wasn\'t found. Check your config.yml file.');
|
||||
});
|
||||
|
||||
it('should throw if collections not an array in config', () => {
|
||||
expect(() => {
|
||||
validateConfig(fromJS({ foo: 'bar', backend: { name: 'bar' }, media_folder: 'baz', collections: {} }));
|
||||
}).toThrowError('Error in configuration file: Your `collections` must be an array with at least one element. Check your config.yml file.');
|
||||
});
|
||||
|
||||
it('should throw if collections is an empty array in config', () => {
|
||||
expect(() => {
|
||||
validateConfig(fromJS({ foo: 'bar', backend: { name: 'bar' }, media_folder: 'baz', collections: [] }));
|
||||
}).toThrowError('Error in configuration file: Your `collections` must be an array with at least one element. Check your config.yml file.');
|
||||
});
|
||||
|
||||
it('should throw if collections is an array with a single null element in config', () => {
|
||||
expect(() => {
|
||||
validateConfig(fromJS({ foo: 'bar', backend: { name: 'bar' }, media_folder: 'baz', collections: [null] }));
|
||||
}).toThrowError('Error in configuration file: Your `collections` must be an array with at least one element. Check your config.yml file.');
|
||||
});
|
||||
});
|
||||
});
|
@ -1,11 +1,9 @@
|
||||
import yaml from "js-yaml";
|
||||
import { Map, List, fromJS } from "immutable";
|
||||
import { trimStart, flow, isBoolean, get } from "lodash";
|
||||
import { Map, fromJS } from "immutable";
|
||||
import { trimStart, flow, get } from "lodash";
|
||||
import { authenticateUser } from "Actions/auth";
|
||||
import { formatByExtension, supportedFormats, frontmatterFormats } from "Formats/formats";
|
||||
import { selectIdentifier } from "Reducers/collections";
|
||||
import { IDENTIFIER_FIELDS } from "Constants/fieldInference";
|
||||
import * as publishModes from "Constants/publishModes";
|
||||
import { validateConfig } from 'Constants/configSchema';
|
||||
|
||||
export const CONFIG_REQUEST = "CONFIG_REQUEST";
|
||||
export const CONFIG_SUCCESS = "CONFIG_SUCCESS";
|
||||
@ -43,76 +41,6 @@ export function applyDefaults(config) {
|
||||
});
|
||||
}
|
||||
|
||||
function validateCollection(collection) {
|
||||
const {
|
||||
name,
|
||||
folder,
|
||||
files,
|
||||
format,
|
||||
extension,
|
||||
frontmatter_delimiter: delimiter,
|
||||
} = collection.toJS();
|
||||
|
||||
if (!folder && !files) {
|
||||
throw new Error(`Unknown collection type for collection "${name}". Collections can be either Folder based or File based.`);
|
||||
}
|
||||
if (format && !supportedFormats.includes(format)) {
|
||||
throw new Error(`Unknown collection format for collection "${name}". Supported formats are ${supportedFormats.join(',')}`);
|
||||
}
|
||||
if (!format && extension && !formatByExtension(extension)) {
|
||||
// Cannot infer format from extension.
|
||||
throw new Error(`Please set a format for collection "${name}". Supported formats are ${supportedFormats.join(',')}`);
|
||||
}
|
||||
if (delimiter && !frontmatterFormats.includes(format)) {
|
||||
// Cannot set custom delimiter without explicit and proper frontmatter format declaration
|
||||
throw new Error(`Please set a proper frontmatter format for collection "${name}" to use a custom delimiter. Supported frontmatter formats are yaml-frontmatter, toml-frontmatter, and json-frontmatter.`);
|
||||
}
|
||||
if (folder && !selectIdentifier(collection)) {
|
||||
// Verify that folder-type collections have an identifier field for slug creation.
|
||||
throw new Error(`Collection "${name}" must have a field that is a valid entry identifier. Supported fields are ${IDENTIFIER_FIELDS.join(', ')}.`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function validateConfig(config) {
|
||||
if (!config.get('backend')) {
|
||||
throw new Error("Error in configuration file: A `backend` wasn't found. Check your config.yml file.");
|
||||
}
|
||||
if (!config.getIn(['backend', 'name'])) {
|
||||
throw new Error("Error in configuration file: A `backend.name` wasn't found. Check your config.yml file.");
|
||||
}
|
||||
if (typeof config.getIn(['backend', 'name']) !== 'string') {
|
||||
throw new Error("Error in configuration file: Your `backend.name` must be a string. Check your config.yml file.");
|
||||
}
|
||||
if (!config.get('media_folder')) {
|
||||
throw new Error("Error in configuration file: A `media_folder` wasn't found. Check your config.yml file.");
|
||||
}
|
||||
if (typeof config.get('media_folder') !== 'string') {
|
||||
throw new Error("Error in configuration file: Your `media_folder` must be a string. Check your config.yml file.");
|
||||
}
|
||||
const slug_encoding = config.getIn(['slug', 'encoding'], "unicode");
|
||||
if (slug_encoding !== "unicode" && slug_encoding !== "ascii") {
|
||||
throw new Error("Error in configuration file: Your `slug.encoding` must be either `unicode` or `ascii`. Check your config.yml file.")
|
||||
}
|
||||
if (!isBoolean(config.getIn(['slug', 'clean_accents'], false))) {
|
||||
throw new Error("Error in configuration file: Your `slug.clean_accents` must be a boolean. Check your config.yml file.");
|
||||
}
|
||||
if (!config.get('collections')) {
|
||||
throw new Error("Error in configuration file: A `collections` wasn't found. Check your config.yml file.");
|
||||
}
|
||||
const collections = config.get('collections');
|
||||
if (!List.isList(collections) || collections.isEmpty() || !collections.first()) {
|
||||
throw new Error("Error in configuration file: Your `collections` must be an array with at least one element. Check your config.yml file.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate Collections
|
||||
*/
|
||||
config.get('collections').forEach(validateCollection);
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
function mergePreloadedConfig(preloadedConfig, loadedConfig) {
|
||||
const map = fromJS(loadedConfig) || Map();
|
||||
return preloadedConfig ? preloadedConfig.mergeDeep(map) : map;
|
||||
@ -190,7 +118,9 @@ export function loadConfig() {
|
||||
* Merge any existing configuration so the result can be validated.
|
||||
*/
|
||||
const mergedConfig = mergePreloadedConfig(preloadedConfig, loadedConfig);
|
||||
const config = flow(validateConfig, applyDefaults)(mergedConfig);
|
||||
validateConfig(mergedConfig.toJS());
|
||||
|
||||
const config = applyDefaults(mergedConfig);
|
||||
|
||||
dispatch(configDidLoad(config));
|
||||
dispatch(authenticateUser());
|
||||
|
@ -38,6 +38,16 @@ const AppMainContainer = styled.div`
|
||||
margin: 0 auto;
|
||||
`
|
||||
|
||||
const ErrorContainer = styled.div`
|
||||
margin: 20px;
|
||||
`
|
||||
|
||||
const ErrorCodeBlock = styled.pre`
|
||||
margin-left: 20px;
|
||||
font-size: 15px;
|
||||
line-height: 1.5;
|
||||
`
|
||||
|
||||
class App extends React.Component {
|
||||
|
||||
static propTypes = {
|
||||
@ -53,15 +63,17 @@ class App extends React.Component {
|
||||
};
|
||||
|
||||
static configError(config) {
|
||||
return (<div>
|
||||
<h1>Error loading the CMS configuration</h1>
|
||||
return (
|
||||
<ErrorContainer>
|
||||
<h1>Error loading the CMS configuration</h1>
|
||||
|
||||
<div>
|
||||
<p>The <code>config.yml</code> file could not be loaded or failed to parse properly.</p>
|
||||
<p><strong>Error message:</strong> {config.get('error')}</p>
|
||||
<p>Check your console for details.</p>
|
||||
</div>
|
||||
</div>);
|
||||
<div>
|
||||
<strong>Config Errors:</strong>
|
||||
<ErrorCodeBlock>{config.get('error')}</ErrorCodeBlock>
|
||||
<span>Check your config.yml file.</span>
|
||||
</div>
|
||||
</ErrorContainer>
|
||||
);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
@ -0,0 +1,84 @@
|
||||
import { validateConfig } from '../configSchema';
|
||||
|
||||
describe('config', () => {
|
||||
/**
|
||||
* Suppress error logging to reduce noise during testing. Jest will still
|
||||
* log test failures and associated errors as expected.
|
||||
*/
|
||||
beforeEach(() => {
|
||||
spyOn(console, 'error')
|
||||
})
|
||||
|
||||
describe('validateConfig', () => {
|
||||
it('should not throw if no errors', () => {
|
||||
const config = {
|
||||
foo: 'bar',
|
||||
backend: { name: 'bar' },
|
||||
media_folder: 'baz',
|
||||
collections: [{
|
||||
name: 'posts',
|
||||
label: 'Posts',
|
||||
folder: '_posts',
|
||||
fields: [{ name: 'title', label: 'title', widget: 'string' }],
|
||||
}],
|
||||
};
|
||||
expect(() => {
|
||||
validateConfig(config);
|
||||
}).not.toThrowError();
|
||||
});
|
||||
|
||||
it('should throw if backend is not defined in config', () => {
|
||||
expect(() => {
|
||||
validateConfig({ foo: 'bar' });
|
||||
}).toThrowError("config should have required property 'backend'");
|
||||
});
|
||||
|
||||
it('should throw if backend name is not defined in config', () => {
|
||||
expect(() => {
|
||||
validateConfig({ foo: 'bar', backend: {} });
|
||||
}).toThrowError("'backend' should have required property 'name'");
|
||||
});
|
||||
|
||||
it('should throw if backend name is not a string in config', () => {
|
||||
expect(() => {
|
||||
validateConfig({ foo: 'bar', backend: { name: { } } });
|
||||
}).toThrowError("'backend.name' should be string");
|
||||
});
|
||||
|
||||
it('should throw if media_folder is not defined in config', () => {
|
||||
expect(() => {
|
||||
validateConfig({ foo: 'bar', backend: { name: 'bar' } });
|
||||
}).toThrowError("config should have required property 'media_folder'");
|
||||
});
|
||||
|
||||
it('should throw if media_folder is not a string in config', () => {
|
||||
expect(() => {
|
||||
validateConfig({ foo: 'bar', backend: { name: 'bar' }, media_folder: {} });
|
||||
}).toThrowError("'media_folder' should be string");
|
||||
});
|
||||
|
||||
it('should throw if collections is not defined in config', () => {
|
||||
expect(() => {
|
||||
validateConfig({ foo: 'bar', backend: { name: 'bar' }, media_folder: 'baz' });
|
||||
}).toThrowError("config should have required property 'collections'");
|
||||
});
|
||||
|
||||
it('should throw if collections not an array in config', () => {
|
||||
expect(() => {
|
||||
validateConfig({ foo: 'bar', backend: { name: 'bar' }, media_folder: 'baz', collections: {} });
|
||||
}).toThrowError("'collections' should be array");
|
||||
});
|
||||
|
||||
it('should throw if collections is an empty array in config', () => {
|
||||
expect(() => {
|
||||
validateConfig({ foo: 'bar', backend: { name: 'bar' }, media_folder: 'baz', collections: [] });
|
||||
}).toThrowError("'collections' should NOT have less than 1 items");
|
||||
});
|
||||
|
||||
it('should throw if collections is an array with a single null element in config', () => {
|
||||
expect(() => {
|
||||
validateConfig({ foo: 'bar', backend: { name: 'bar' }, media_folder: 'baz', collections: [null] });
|
||||
}).toThrowError("'collections[0]' should be object");
|
||||
});
|
||||
});
|
||||
});
|
175
packages/netlify-cms-core/src/constants/configSchema.js
Normal file
175
packages/netlify-cms-core/src/constants/configSchema.js
Normal file
@ -0,0 +1,175 @@
|
||||
import AJV from 'ajv';
|
||||
import ajvErrors from 'ajv-errors';
|
||||
import {
|
||||
formatExtensions,
|
||||
frontmatterFormats,
|
||||
extensionFormatters,
|
||||
} from "Formats/formats";
|
||||
import { IDENTIFIER_FIELDS } from "Constants/fieldInference";
|
||||
|
||||
/**
|
||||
* Config for fields in both file and folder collections.
|
||||
*/
|
||||
const fieldsConfig = {
|
||||
type: "array",
|
||||
minItems: 1,
|
||||
items: {
|
||||
// ------- Each field: -------
|
||||
type: "object",
|
||||
properties: {
|
||||
name: { type: "string" },
|
||||
label: { type: "string" },
|
||||
widget: { type: "string" },
|
||||
required: { type: "boolean" },
|
||||
},
|
||||
required: ["name"],
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 = () => ({
|
||||
type: "object",
|
||||
properties: {
|
||||
backend: {
|
||||
type: "object",
|
||||
properties: { name: { type: "string", examples: ["test-repo"] } },
|
||||
required: ["name"],
|
||||
},
|
||||
display_url: { type: "string", examples: ["https://example.com"] },
|
||||
media_folder: { type: "string", examples: ["assets/uploads"] },
|
||||
public_folder: { type: "string", examples: ["/uploads"] },
|
||||
publish_mode: {
|
||||
type: "string",
|
||||
enum: ["editorial_workflow"],
|
||||
examples: ["editorial_workflow"],
|
||||
},
|
||||
slug: {
|
||||
type: "object",
|
||||
properties: {
|
||||
encoding: { type: "string", enum: ["unicode", "ascii"] },
|
||||
clean_accents: { type: "boolean" },
|
||||
},
|
||||
},
|
||||
collections: {
|
||||
type: "array",
|
||||
minItems: 1,
|
||||
items: {
|
||||
// ------- Each collection: -------
|
||||
type: "object",
|
||||
properties: {
|
||||
name: { type: "string" },
|
||||
label: { type: "string" },
|
||||
label_singular: { type: "string" },
|
||||
description: { type: "string" },
|
||||
folder: { type: "string" },
|
||||
files: {
|
||||
type: "array",
|
||||
items: {
|
||||
// ------- Each file: -------
|
||||
type: "object",
|
||||
properties: {
|
||||
name: { type: "string" },
|
||||
label: { type: "string" },
|
||||
label_singular: { type: "string" },
|
||||
description: { type: "string" },
|
||||
file: { type: "string" },
|
||||
fields: fieldsConfig,
|
||||
},
|
||||
required: ["name", "label", "file", "fields"],
|
||||
},
|
||||
},
|
||||
slug: { type: "string" },
|
||||
create: { type: "boolean" },
|
||||
editor: {
|
||||
type: "object",
|
||||
properties: {
|
||||
preview: { type: "boolean" },
|
||||
},
|
||||
},
|
||||
format: { type: "string", enum: Object.keys(formatExtensions) },
|
||||
extension: { type: "string" },
|
||||
frontmatter_delimiter: { type: "string" },
|
||||
fields: fieldsConfig,
|
||||
},
|
||||
required: ["name", "label"],
|
||||
oneOf: [{ required: ["files"] }, { required: ["folder", "fields"] }],
|
||||
if: { required: ["extension"] },
|
||||
then: {
|
||||
// Cannot infer format from extension.
|
||||
if: {
|
||||
properties: {
|
||||
extension: { enum: Object.keys(extensionFormatters) },
|
||||
},
|
||||
},
|
||||
else: { required: ["format"] },
|
||||
},
|
||||
dependencies: {
|
||||
frontmatter_delimiter: {
|
||||
properties: {
|
||||
format: { enum: frontmatterFormats },
|
||||
},
|
||||
required: ["format"],
|
||||
},
|
||||
folder: {
|
||||
errorMessage: {
|
||||
_: 'must have a field that is a valid entry identifier',
|
||||
},
|
||||
properties: {
|
||||
fields: {
|
||||
contains: {
|
||||
properties: {
|
||||
name: { enum: IDENTIFIER_FIELDS },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
required: ["backend", "media_folder", "collections"],
|
||||
});
|
||||
|
||||
class ConfigError extends Error {
|
||||
constructor(errors, ...args) {
|
||||
const message = errors
|
||||
.map(({ message, dataPath }) => {
|
||||
const dotPath = dataPath
|
||||
.slice(1)
|
||||
.split("/")
|
||||
.map(seg => (seg.match(/^\d+$/) ? `[${seg}]` : `.${seg}`))
|
||||
.join("")
|
||||
.slice(1);
|
||||
return `${dotPath ? `'${dotPath}'` : "config"} ${message}`;
|
||||
})
|
||||
.join("\n");
|
||||
super(message, ...args);
|
||||
|
||||
this.errors = errors;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this.message;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `validateConfig` is a pure function. It does not mutate
|
||||
* the config that is passed in.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
import { List } from 'immutable';
|
||||
import { get } from 'lodash';
|
||||
import yamlFormatter from './yaml';
|
||||
import tomlFormatter from './toml';
|
||||
import jsonFormatter from './json';
|
||||
@ -6,18 +7,7 @@ import { FrontmatterInfer, frontmatterJSON, frontmatterTOML, frontmatterYAML } f
|
||||
|
||||
export const frontmatterFormats = ['yaml-frontmatter','toml-frontmatter','json-frontmatter']
|
||||
|
||||
export const supportedFormats = [
|
||||
'yml',
|
||||
'yaml',
|
||||
'toml',
|
||||
'json',
|
||||
'frontmatter',
|
||||
'json-frontmatter',
|
||||
'toml-frontmatter',
|
||||
'yaml-frontmatter',
|
||||
];
|
||||
|
||||
export const formatToExtension = format => ({
|
||||
export const formatExtensions = {
|
||||
yml: 'yml',
|
||||
yaml: 'yml',
|
||||
toml: 'toml',
|
||||
@ -26,32 +16,28 @@ export const formatToExtension = format => ({
|
||||
'json-frontmatter': 'md',
|
||||
'toml-frontmatter': 'md',
|
||||
'yaml-frontmatter': 'md',
|
||||
}[format]);
|
||||
};
|
||||
|
||||
export function formatByExtension(extension) {
|
||||
return {
|
||||
yml: yamlFormatter,
|
||||
yaml: yamlFormatter,
|
||||
toml: tomlFormatter,
|
||||
json: jsonFormatter,
|
||||
md: FrontmatterInfer,
|
||||
markdown: FrontmatterInfer,
|
||||
html: FrontmatterInfer,
|
||||
}[extension];
|
||||
}
|
||||
export const extensionFormatters = {
|
||||
yml: yamlFormatter,
|
||||
yaml: yamlFormatter,
|
||||
toml: tomlFormatter,
|
||||
json: jsonFormatter,
|
||||
md: FrontmatterInfer,
|
||||
markdown: FrontmatterInfer,
|
||||
html: FrontmatterInfer,
|
||||
};
|
||||
|
||||
function formatByName(name, customDelimiter) {
|
||||
return {
|
||||
yml: yamlFormatter,
|
||||
yaml: yamlFormatter,
|
||||
toml: tomlFormatter,
|
||||
json: jsonFormatter,
|
||||
frontmatter: FrontmatterInfer,
|
||||
'json-frontmatter': frontmatterJSON(customDelimiter),
|
||||
'toml-frontmatter': frontmatterTOML(customDelimiter),
|
||||
'yaml-frontmatter': frontmatterYAML(customDelimiter),
|
||||
}[name];
|
||||
}
|
||||
const formatByName = (name, customDelimiter) => ({
|
||||
yml: yamlFormatter,
|
||||
yaml: yamlFormatter,
|
||||
toml: tomlFormatter,
|
||||
json: jsonFormatter,
|
||||
frontmatter: FrontmatterInfer,
|
||||
'json-frontmatter': frontmatterJSON(customDelimiter),
|
||||
'toml-frontmatter': frontmatterTOML(customDelimiter),
|
||||
'yaml-frontmatter': frontmatterYAML(customDelimiter),
|
||||
}[name]);
|
||||
|
||||
export function resolveFormat(collectionOrEntity, entry) {
|
||||
// Check for custom delimiter
|
||||
@ -68,14 +54,14 @@ export function resolveFormat(collectionOrEntity, entry) {
|
||||
const filePath = entry && entry.path;
|
||||
if (filePath) {
|
||||
const fileExtension = filePath.split('.').pop();
|
||||
return formatByExtension(fileExtension);
|
||||
return get(extensionFormatters, fileExtension);
|
||||
}
|
||||
|
||||
// If creating a new file, and an `extension` is specified in the
|
||||
// collection config, infer the format from that extension.
|
||||
const extension = collectionOrEntity.get('extension');
|
||||
if (extension) {
|
||||
return formatByExtension(extension);
|
||||
return get(extensionFormatters, extension);
|
||||
}
|
||||
|
||||
// If no format is specified and it cannot be inferred, return the default.
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { List } from 'immutable';
|
||||
import { escapeRegExp } from 'lodash';
|
||||
import { get, escapeRegExp } from 'lodash';
|
||||
import consoleError from 'Lib/consoleError';
|
||||
import { CONFIG_SUCCESS } from 'Actions/config';
|
||||
import { FILES, FOLDER } from 'Constants/collectionTypes';
|
||||
import { INFERABLE_FIELDS, IDENTIFIER_FIELDS } from 'Constants/fieldInference';
|
||||
import { formatToExtension } from 'Formats/formats';
|
||||
import { formatExtensions } from 'Formats/formats';
|
||||
|
||||
const collections = (state = null, action) => {
|
||||
switch (action.type) {
|
||||
@ -30,7 +30,7 @@ const collections = (state = null, action) => {
|
||||
const selectors = {
|
||||
[FOLDER]: {
|
||||
entryExtension(collection) {
|
||||
return (collection.get('extension') || formatToExtension(collection.get('format') || 'frontmatter')).replace(/^\./, '');
|
||||
return (collection.get('extension') || get(formatExtensions, (collection.get('format') || 'frontmatter'))).replace(/^\./, '');
|
||||
},
|
||||
fields(collection) {
|
||||
return collection.get('fields');
|
||||
@ -97,6 +97,7 @@ export const selectTemplateName = (collection, slug) => selectors[collection.get
|
||||
export const selectIdentifier = collection => {
|
||||
const fieldNames = collection.get('fields').map(field => field.get('name'));
|
||||
return IDENTIFIER_FIELDS.find(id => fieldNames.find(name => name.toLowerCase().trim() === id));
|
||||
// There must be a field whose `name` matches one of the IDENTIFIER_FIELDS.
|
||||
};
|
||||
export const selectInferedField = (collection, fieldName) => {
|
||||
const inferableField = INFERABLE_FIELDS[fieldName];
|
||||
|
@ -929,6 +929,10 @@ add-stream@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/add-stream/-/add-stream-1.0.0.tgz#6a7990437ca736d5e1288db92bd3266d5f5cb2aa"
|
||||
|
||||
ajv-errors@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.0.tgz#ecf021fa108fd17dfb5e6b383f2dd233e31ffc59"
|
||||
|
||||
ajv-keywords@^3.1.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.2.0.tgz#e86b819c602cf8821ad637413698f1dec021847a"
|
||||
@ -942,7 +946,7 @@ ajv@^5.1.0:
|
||||
fast-json-stable-stringify "^2.0.0"
|
||||
json-schema-traverse "^0.3.0"
|
||||
|
||||
ajv@^6.1.0:
|
||||
ajv@^6.1.0, ajv@^6.4.0:
|
||||
version "6.5.2"
|
||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.5.2.tgz#678495f9b82f7cca6be248dd92f59bff5e1f4360"
|
||||
dependencies:
|
||||
|
Loading…
x
Reference in New Issue
Block a user