Skip to content

Commit

Permalink
refactor: simplify plugin validation
Browse files Browse the repository at this point in the history
  • Loading branch information
pvdlg committed Jul 10, 2018
1 parent f7f4aab commit 576eb60
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 167 deletions.
24 changes: 12 additions & 12 deletions lib/definitions/errors.js
Expand Up @@ -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}`;

Expand Down Expand Up @@ -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}\``
Expand All @@ -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\`.
Expand Down
47 changes: 11 additions & 36 deletions lib/definitions/plugins.js
Expand Up @@ -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)),
},
};
19 changes: 9 additions & 10 deletions lib/plugins/index.js
Expand Up @@ -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;
Expand Down
18 changes: 9 additions & 9 deletions lib/plugins/normalize.js
Expand Up @@ -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;
}
Expand All @@ -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);
}
}

Expand All @@ -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) {
Expand Down

0 comments on commit 576eb60

Please sign in to comment.