feat: add new plugins option

This commit is contained in:
Pierre Vanduynslager
2018-10-08 13:24:51 -04:00
parent 9930dac69e
commit 5ba5010c80
18 changed files with 717 additions and 213 deletions
+9 -1
View File
@@ -61,9 +61,17 @@ Your configuration for the \`tagFormat\` option is \`${stringify(tagFormat)}\`.`
required ? 'is required and ' : ''
}must be ${
multiple ? 'a single or an array of plugins' : 'a single plugin'
} definition. A plugin definition is either a string or an object with a \`path\` property.
} definition. A plugin definition is an npm module name, optionnaly wrapped in an array with an object.
Your configuration for the \`${type}\` plugin is \`${stringify(pluginConf)}\`.`,
}),
EPLUGINSCONF: ({plugin}) => ({
message: 'The `plugins` configuration is invalid.',
details: `The [plugins](${linkify(
'docs/usage/configuration.md#plugins'
)}) option must be an array of plugin definions. A plugin definition is an npm module name, optionnaly wrapped in an array with an object.
The invalid configuration is \`${stringify(plugin)}\`.`,
}),
EPLUGIN: ({pluginName, type}) => ({
message: `A plugin configured in the step ${type} is not a valid semantic-release plugin.`,
+1 -8
View File
@@ -6,13 +6,12 @@ const {RELEASE_TYPE, RELEASE_NOTES_SEPARATOR} = require('./constants');
module.exports = {
verifyConditions: {
default: ['@semantic-release/npm', '@semantic-release/github'],
multiple: true,
required: false,
pipelineConfig: () => ({settleAll: true}),
},
analyzeCommits: {
default: '@semantic-release/commit-analyzer',
default: ['@semantic-release/commit-analyzer'],
multiple: false,
required: true,
outputValidator: output => !output || RELEASE_TYPE.includes(output),
@@ -23,13 +22,11 @@ module.exports = {
postprocess: ([result]) => result,
},
verifyRelease: {
default: false,
multiple: true,
required: false,
pipelineConfig: () => ({settleAll: true}),
},
generateNotes: {
default: ['@semantic-release/release-notes-generator'],
multiple: true,
required: false,
outputValidator: output => !output || isString(output),
@@ -45,7 +42,6 @@ module.exports = {
postprocess: (results, {env}) => hideSensitive(env)(results.filter(Boolean).join(RELEASE_NOTES_SEPARATOR)),
},
prepare: {
default: ['@semantic-release/npm'],
multiple: true,
required: false,
pipelineConfig: ({generateNotes}, logger) => ({
@@ -64,7 +60,6 @@ module.exports = {
}),
},
publish: {
default: ['@semantic-release/npm', '@semantic-release/github'],
multiple: true,
required: false,
outputValidator: output => !output || isPlainObject(output),
@@ -78,14 +73,12 @@ module.exports = {
}),
},
success: {
default: ['@semantic-release/github'],
multiple: true,
required: false,
pipelineConfig: () => ({settleAll: true}),
preprocess: ({releases, env, ...inputs}) => ({...inputs, env, releases: hideSensitiveValues(env, releases)}),
},
fail: {
default: ['@semantic-release/github'],
multiple: true,
required: false,
pipelineConfig: () => ({settleAll: true}),
+9 -3
View File
@@ -1,4 +1,4 @@
const {castArray, pickBy, isUndefined, isNull, isString, isPlainObject} = require('lodash');
const {castArray, pickBy, isNil, isString, isPlainObject} = require('lodash');
const readPkgUp = require('read-pkg-up');
const cosmiconfig = require('cosmiconfig');
const resolveFrom = require('resolve-from');
@@ -35,7 +35,7 @@ module.exports = async (context, opts) => {
// For each plugin defined in a shareable config, save in `pluginsPath` the extendable config path,
// so those plugin will be loaded relatively to the config file
Object.entries(extendsOpts).reduce((pluginsPath, [option, value]) => {
if (PLUGINS_DEFINITIONS[option]) {
if (PLUGINS_DEFINITIONS[option] || option === 'plugins') {
castArray(value)
.filter(plugin => isString(plugin) || (isPlainObject(plugin) && isString(plugin.path)))
.map(plugin => (isString(plugin) ? plugin : plugin.path))
@@ -57,8 +57,14 @@ module.exports = async (context, opts) => {
branch: 'master',
repositoryUrl: (await pkgRepoUrl({normalize: false, cwd})) || (await repoUrl({cwd, env})),
tagFormat: `v\${version}`,
plugins: [
'@semantic-release/commit-analyzer',
'@semantic-release/release-notes-generator',
'@semantic-release/npm',
'@semantic-release/github',
],
// Remove `null` and `undefined` options so they can be replaced with default ones
...pickBy(options, option => !isUndefined(option) && !isNull(option)),
...pickBy(options, option => !isNil(option)),
};
debug('options values: %O', options);
+2 -2
View File
@@ -1,5 +1,5 @@
const {parse, format} = require('url');
const {isUndefined} = require('lodash');
const {isNil} = require('lodash');
const gitUrlParse = require('git-url-parse');
const hostedGitInfo = require('hosted-git-info');
const {verifyAuth} = require('./git');
@@ -44,7 +44,7 @@ module.exports = async ({cwd, env, options: {repositoryUrl, branch}}) => {
try {
await verifyAuth(repositoryUrl, branch, {cwd, env});
} catch (error) {
const envVar = Object.keys(GIT_TOKENS).find(envVar => !isUndefined(env[envVar]));
const envVar = Object.keys(GIT_TOKENS).find(envVar => !isNil(env[envVar]));
const gitCredentials = `${GIT_TOKENS[envVar] || ''}${env[envVar] || ''}`;
const {protocols, ...parsed} = gitUrlParse(repositoryUrl);
const protocol = protocols.includes('https') ? 'https' : protocols.includes('http') ? 'http' : 'https';
+55 -18
View File
@@ -1,52 +1,89 @@
const {identity, isPlainObject, omit, castArray, isUndefined} = require('lodash');
const {identity, isPlainObject, omit, castArray, isNil, isString} = require('lodash');
const AggregateError = require('aggregate-error');
const getError = require('../get-error');
const PLUGINS_DEFINITIONS = require('../definitions/plugins');
const {validateConfig} = require('./utils');
const {validatePlugin, validateStep, loadPlugin, parseConfig} = require('./utils');
const pipeline = require('./pipeline');
const normalize = require('./normalize');
module.exports = (context, pluginsPath) => {
const {options, logger} = context;
let {options, logger} = context;
const errors = [];
const plugins = Object.entries(PLUGINS_DEFINITIONS).reduce(
const plugins = options.plugins
? castArray(options.plugins).reduce((plugins, plugin) => {
if (validatePlugin(plugin)) {
const [name, config] = parseConfig(plugin);
plugin = isString(name) ? loadPlugin(context, name, pluginsPath) : name;
if (isPlainObject(plugin)) {
Object.entries(plugin).forEach(([type, func]) => {
if (PLUGINS_DEFINITIONS[type]) {
plugins[type] = [...(plugins[type] || []), [func, config]];
}
});
} else {
errors.push(getError('EPLUGINSCONF', {plugin}));
}
} else {
errors.push(getError('EPLUGINSCONF', {plugin}));
}
return plugins;
}, {})
: [];
if (errors.length > 0) {
throw new AggregateError(errors);
}
options = {...plugins, ...options};
const pluginsConf = Object.entries(PLUGINS_DEFINITIONS).reduce(
(
plugins,
pluginsConf,
[type, {multiple, required, default: def, pipelineConfig, postprocess = identity, preprocess = identity}]
) => {
let pluginOpts;
if (isUndefined(options[type])) {
if (isNil(options[type]) && def) {
pluginOpts = def;
} else {
const defaultPaths = castArray(def);
// If an object is passed and the path is missing, set the default one for single plugins
if (isPlainObject(options[type]) && !options[type].path && defaultPaths.length === 1) {
[options[type].path] = defaultPaths;
// If an object is passed and the path is missing, merge it with step options
if (isPlainObject(options[type]) && !options[type].path) {
options[type] = castArray(plugins[type]).map(
plugin => (plugin ? [plugin[0], Object.assign(plugin[1], options[type])] : plugin)
);
}
if (!validateConfig({multiple, required}, options[type])) {
if (!validateStep({multiple, required}, options[type])) {
errors.push(getError('EPLUGINCONF', {type, multiple, required, pluginConf: options[type]}));
return plugins;
return pluginsConf;
}
pluginOpts = options[type];
}
const steps = castArray(pluginOpts).map(pluginOpt =>
normalize({...context, options: omit(options, Object.keys(PLUGINS_DEFINITIONS))}, type, pluginOpt, pluginsPath)
normalize(
{...context, options: omit(options, Object.keys(PLUGINS_DEFINITIONS), 'plugins')},
type,
pluginOpt,
pluginsPath
)
);
plugins[type] = async input =>
pluginsConf[type] = async input =>
postprocess(
await pipeline(steps, pipelineConfig && pipelineConfig(plugins, logger))(await preprocess(input)),
await pipeline(steps, pipelineConfig && pipelineConfig(pluginsConf, logger))(await preprocess(input)),
input
);
return plugins;
return pluginsConf;
},
{}
plugins
);
if (errors.length > 0) {
throw new AggregateError(errors);
}
return plugins;
return pluginsConf;
};
+6 -10
View File
@@ -1,22 +1,18 @@
const {dirname} = require('path');
const {isString, isPlainObject, isFunction, noop, cloneDeep, omit} = require('lodash');
const resolveFrom = require('resolve-from');
const {isPlainObject, isFunction, noop, cloneDeep, omit} = require('lodash');
const getError = require('../get-error');
const {extractErrors} = require('../utils');
const PLUGINS_DEFINITIONS = require('../definitions/plugins');
const {loadPlugin, parseConfig} = require('./utils');
module.exports = ({cwd, stdout, stderr, options, logger}, type, pluginOpt, pluginsPath) => {
module.exports = (context, type, pluginOpt, pluginsPath) => {
const {stdout, stderr, options, logger} = context;
if (!pluginOpt) {
return noop;
}
const {path, ...config} = isString(pluginOpt) || isFunction(pluginOpt) ? {path: pluginOpt} : pluginOpt;
const [path, config] = parseConfig(pluginOpt);
const pluginName = isFunction(path) ? `[Function: ${path.name}]` : path;
const basePath = pluginsPath[path]
? dirname(resolveFrom.silent(__dirname, pluginsPath[path]) || resolveFrom(cwd, pluginsPath[path]))
: __dirname;
const plugin = isFunction(path) ? path : require(resolveFrom.silent(basePath, path) || resolveFrom(cwd, path));
const plugin = loadPlugin(context, path, pluginsPath);
let func;
if (isFunction(plugin)) {
+59 -9
View File
@@ -1,18 +1,68 @@
const {isString, isFunction, castArray} = require('lodash');
const {dirname} = require('path');
const {isString, isFunction, castArray, isArray, isPlainObject, isNil} = require('lodash');
const resolveFrom = require('resolve-from');
const validateSingleConfig = conf => {
const validateStepArrayDefinition = conf =>
isArray(conf) &&
(conf.length === 1 || conf.length === 2) &&
(isString(conf[0]) || isFunction(conf[0])) &&
(isNil(conf[1]) || isPlainObject(conf[1]));
const validateSingleStep = conf => {
if (validateStepArrayDefinition(conf)) {
return true;
}
conf = castArray(conf);
return conf.length === 1 && (isString(conf[0]) || isString(conf[0].path) || isFunction(conf[0]));
if (conf.length !== 1) {
return false;
}
const [path, config] = parseConfig(conf[0]);
return (isString(path) || isFunction(path)) && isPlainObject(config);
};
const validateMultipleConfig = conf => castArray(conf).every(conf => validateSingleConfig(conf));
const validateMultipleStep = conf => {
return conf.every(conf => validateSingleStep(conf));
};
const validateConfig = ({multiple, required}, conf) => {
function validatePlugin(conf) {
return (
isString(conf) ||
(isArray(conf) &&
(conf.length === 1 || conf.length === 2) &&
(isString(conf[0]) || isPlainObject(conf[0])) &&
(isNil(conf[1]) || isPlainObject(conf[1]))) ||
(isPlainObject(conf) && (isNil(conf.path) || isString(conf.path) || isPlainObject(conf.path)))
);
}
function validateStep({multiple, required}, conf) {
conf = castArray(conf).filter(Boolean);
if (required) {
return Boolean(conf) && conf.length >= 1 && (multiple ? validateMultipleConfig : validateSingleConfig)(conf);
return conf.length >= 1 && (multiple ? validateMultipleStep : validateSingleStep)(conf);
}
return conf.length === 0 || (multiple ? validateMultipleConfig : validateSingleConfig)(conf);
};
return conf.length === 0 || (multiple ? validateMultipleStep : validateSingleStep)(conf);
}
module.exports = {validateConfig};
function loadPlugin({cwd}, path, pluginsPath) {
const basePath = pluginsPath[path]
? dirname(resolveFrom.silent(__dirname, pluginsPath[path]) || resolveFrom(cwd, pluginsPath[path]))
: __dirname;
return isFunction(path) ? path : require(resolveFrom.silent(basePath, path) || resolveFrom(cwd, path));
}
function parseConfig(plugin) {
let path;
let config;
if (isArray(plugin)) {
[path, config] = plugin;
} else if (isPlainObject(plugin) && !isNil(plugin.path)) {
({path, ...config} = plugin);
} else {
path = plugin;
}
return [path, config || {}];
}
module.exports = {validatePlugin, validateStep, loadPlugin, parseConfig};