refactor: simplify plugin validation

This commit is contained in:
Pierre Vanduynslager
2018-07-10 13:18:58 -04:00
parent f7f4aabe9e
commit 576eb6027f
7 changed files with 125 additions and 159 deletions
+12 -12
View File
@@ -4,7 +4,7 @@ const {toLower, isString} = require('lodash');
const pkg = require('../../package.json');
const {RELEASE_TYPE} = require('./constants');
const homepage = url.format({...url.parse(pkg.homepage), ...{hash: null}});
const homepage = url.format({...url.parse(pkg.homepage), hash: null});
const stringify = obj => (isString(obj) ? obj : inspect(obj, {breakLength: Infinity, depth: 2, maxArrayLength: 5}));
const linkify = file => `${homepage}/blob/caribou/${file}`;
@@ -55,25 +55,25 @@ Your configuration for the \`tagFormat\` option is \`${stringify(tagFormat)}\`.`
Your configuration for the \`tagFormat\` option is \`${stringify(tagFormat)}\`.`,
}),
EPLUGINCONF: ({pluginType, pluginConf}) => ({
message: `The \`${pluginType}\` plugin configuration is invalid.`,
details: `The [${pluginType} plugin configuration](${linkify(
`docs/usage/plugins.md#${toLower(pluginType)}-plugin`
EPLUGINCONF: ({type, pluginConf}) => ({
message: `The \`${type}\` plugin configuration is invalid.`,
details: `The [${type} plugin configuration](${linkify(
`docs/usage/plugins.md#${toLower(type)}-plugin`
)}) if defined, must be a single or an array of plugins definition. A plugin definition is either a string or an object with a \`path\` property.
Your configuration for the \`${pluginType}\` plugin is \`${stringify(pluginConf)}\`.`,
Your configuration for the \`${type}\` plugin is \`${stringify(pluginConf)}\`.`,
}),
EPLUGIN: ({pluginName, pluginType}) => ({
message: `A plugin configured in the step ${pluginType} is not a valid semantic-release plugin.`,
details: `A valid \`${pluginType}\` **semantic-release** plugin must be a function or an object with a function in the property \`${pluginType}\`.
EPLUGIN: ({pluginName, type}) => ({
message: `A plugin configured in the step ${type} is not a valid semantic-release plugin.`,
details: `A valid \`${type}\` **semantic-release** plugin must be a function or an object with a function in the property \`${type}\`.
The plugin \`${pluginName}\` doesn't have the property \`${pluginType}\` and cannot be used for the \`${pluginType}\` step.
The plugin \`${pluginName}\` doesn't have the property \`${type}\` and cannot be used for the \`${type}\` step.
Please refer to the \`${pluginName}\` and [semantic-release plugins configuration](${linkify(
'docs/usage/plugins.md'
)}) documentation for more details.`,
}),
EANALYZEOUTPUT: ({result, pluginName}) => ({
EANALYZECOMMITSOUTPUT: ({result, pluginName}) => ({
message: 'The `analyzeCommits` plugin returned an invalid value. It must return a valid semver release type.',
details: `The \`analyzeCommits\` plugin must return a valid [semver](https://semver.org) release type. The valid values are: ${RELEASE_TYPE.map(
type => `\`${type}\``
@@ -89,7 +89,7 @@ We recommend to report the issue to the \`${pluginName}\` authors, providing the
'docs/developer-guide/plugin.md'
)})`,
}),
ERELEASENOTESOUTPUT: ({result, pluginName}) => ({
EGENERATENOTESOUTPUT: ({result, pluginName}) => ({
message: 'The `generateNotes` plugin returned an invalid value. It must return a `String`.',
details: `The \`generateNotes\` plugin must return a \`String\`.
+11 -36
View File
@@ -6,62 +6,37 @@ const validatePluginConfig = conf => isString(conf) || isString(conf.path) || is
module.exports = {
verifyConditions: {
default: ['@semantic-release/npm', '@semantic-release/github'],
config: {
validator: conf => !conf || (isArray(conf) ? conf : [conf]).every(conf => validatePluginConfig(conf)),
},
configValidator: conf => !conf || (isArray(conf) ? conf : [conf]).every(conf => validatePluginConfig(conf)),
},
analyzeCommits: {
default: '@semantic-release/commit-analyzer',
config: {
validator: conf => Boolean(conf) && validatePluginConfig(conf),
},
output: {
validator: output => !output || RELEASE_TYPE.includes(output),
error: 'EANALYZEOUTPUT',
},
configValidator: conf => Boolean(conf) && validatePluginConfig(conf),
outputValidator: output => !output || RELEASE_TYPE.includes(output),
},
verifyRelease: {
default: false,
config: {
validator: conf => !conf || (isArray(conf) ? conf : [conf]).every(conf => validatePluginConfig(conf)),
},
configValidator: conf => !conf || (isArray(conf) ? conf : [conf]).every(conf => validatePluginConfig(conf)),
},
generateNotes: {
default: '@semantic-release/release-notes-generator',
config: {
validator: conf => !conf || validatePluginConfig(conf),
},
output: {
validator: output => !output || isString(output),
error: 'ERELEASENOTESOUTPUT',
},
configValidator: conf => !conf || validatePluginConfig(conf),
outputValidator: output => !output || isString(output),
},
prepare: {
default: ['@semantic-release/npm'],
config: {
validator: conf => !conf || (isArray(conf) ? conf : [conf]).every(conf => validatePluginConfig(conf)),
},
configValidator: conf => !conf || (isArray(conf) ? conf : [conf]).every(conf => validatePluginConfig(conf)),
},
publish: {
default: ['@semantic-release/npm', '@semantic-release/github'],
config: {
validator: conf => !conf || (isArray(conf) ? conf : [conf]).every(conf => validatePluginConfig(conf)),
},
output: {
validator: output => !output || isPlainObject(output),
error: 'EPUBLISHOUTPUT',
},
configValidator: conf => !conf || (isArray(conf) ? conf : [conf]).every(conf => validatePluginConfig(conf)),
outputValidator: output => !output || isPlainObject(output),
},
success: {
default: ['@semantic-release/github'],
config: {
validator: conf => !conf || (isArray(conf) ? conf : [conf]).every(conf => validatePluginConfig(conf)),
},
configValidator: conf => !conf || (isArray(conf) ? conf : [conf]).every(conf => validatePluginConfig(conf)),
},
fail: {
default: ['@semantic-release/github'],
config: {
validator: conf => !conf || (isArray(conf) ? conf : [conf]).every(conf => validatePluginConfig(conf)),
},
configValidator: conf => !conf || (isArray(conf) ? conf : [conf]).every(conf => validatePluginConfig(conf)),
},
};
+9 -10
View File
@@ -7,28 +7,27 @@ const normalize = require('./normalize');
module.exports = (options, pluginsPath, logger) => {
const errors = [];
const plugins = Object.keys(PLUGINS_DEFINITIONS).reduce((plugins, pluginType) => {
const {config, default: def} = PLUGINS_DEFINITIONS[pluginType];
const plugins = Object.entries(PLUGINS_DEFINITIONS).reduce((plugins, [type, {configValidator, default: def}]) => {
let pluginConfs;
if (isUndefined(options[pluginType])) {
if (isUndefined(options[type])) {
pluginConfs = def;
} else {
// If an object is passed and the path is missing, set the default one for single plugins
if (isPlainObject(options[pluginType]) && !options[pluginType].path && castArray(def).length === 1) {
options[pluginType].path = def;
if (isPlainObject(options[type]) && !options[type].path && castArray(def).length === 1) {
options[type].path = def;
}
if (config && !config.validator(options[pluginType])) {
errors.push(getError('EPLUGINCONF', {pluginType, pluginConf: options[pluginType]}));
if (configValidator && !configValidator(options[type])) {
errors.push(getError('EPLUGINCONF', {type, pluginConf: options[type]}));
return plugins;
}
pluginConfs = options[pluginType];
pluginConfs = options[type];
}
const globalOpts = omit(options, Object.keys(PLUGINS_DEFINITIONS));
plugins[pluginType] = pipeline(
castArray(pluginConfs).map(conf => normalize(pluginType, pluginsPath, globalOpts, conf, logger))
plugins[type] = pipeline(
castArray(pluginConfs).map(conf => normalize(type, pluginsPath, globalOpts, conf, logger))
);
return plugins;
+9 -9
View File
@@ -7,7 +7,7 @@ const PLUGINS_DEFINITIONS = require('../definitions/plugins');
/* eslint max-params: ["error", 5] */
module.exports = (pluginType, pluginsPath, globalOpts, pluginOpts, logger) => {
module.exports = (type, pluginsPath, globalOpts, pluginOpts, logger) => {
if (!pluginOpts) {
return noop;
}
@@ -17,9 +17,9 @@ module.exports = (pluginType, pluginsPath, globalOpts, pluginOpts, logger) => {
if (!isFunction(pluginOpts)) {
if (pluginsPath[path]) {
logger.log('Load plugin "%s" from %s in shareable config %s', pluginType, path, pluginsPath[path]);
logger.log('Load plugin "%s" from %s in shareable config %s', type, path, pluginsPath[path]);
} else {
logger.log('Load plugin "%s" from %s', pluginType, path);
logger.log('Load plugin "%s" from %s', type, path);
}
}
@@ -33,18 +33,18 @@ module.exports = (pluginType, pluginsPath, globalOpts, pluginOpts, logger) => {
let func;
if (isFunction(plugin)) {
func = plugin.bind(null, cloneDeep({...globalOpts, ...config}));
} else if (isPlainObject(plugin) && plugin[pluginType] && isFunction(plugin[pluginType])) {
func = plugin[pluginType].bind(null, cloneDeep({...globalOpts, ...config}));
} else if (isPlainObject(plugin) && plugin[type] && isFunction(plugin[type])) {
func = plugin[type].bind(null, cloneDeep({...globalOpts, ...config}));
} else {
throw getError('EPLUGIN', {pluginType, pluginName});
throw getError('EPLUGIN', {type, pluginName});
}
const validator = async input => {
const definition = PLUGINS_DEFINITIONS[pluginType];
const {outputValidator} = PLUGINS_DEFINITIONS[type] || {};
try {
const result = await func(cloneDeep(input));
if (definition && definition.output && !definition.output.validator(result)) {
throw getError(PLUGINS_DEFINITIONS[pluginType].output.error, {result, pluginName});
if (outputValidator && !outputValidator(result)) {
throw getError(`E${type.toUpperCase()}OUTPUT`, {result, pluginName});
}
return result;
} catch (err) {