feat: add new plugins option
This commit is contained in:
@@ -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.`,
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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
@@ -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};
|
||||
|
||||
Reference in New Issue
Block a user