feat: allow to define plugin options globally
This commit is contained in:
		
							parent
							
								
									d28b7e3e07
								
							
						
					
					
						commit
						f707b1a90a
					
				
							
								
								
									
										13
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								README.md
									
									
									
									
									
								
							| @ -173,14 +173,12 @@ These options are currently available: | |||||||
| 
 | 
 | ||||||
| _A few notes on `npm` config_: | _A few notes on `npm` config_: | ||||||
| 1. The `npm` token can only be defined in the environment as `NPM_TOKEN`, because that’s where `npm` itself is going to read it from. | 1. The `npm` token can only be defined in the environment as `NPM_TOKEN`, because that’s where `npm` itself is going to read it from. | ||||||
| 
 |  | ||||||
| 2. In order to publish to a different `npm` registry you can specify that inside the `package.json`’s [`publishConfig`](https://docs.npmjs.com/files/package.json#publishconfig) field. | 2. In order to publish to a different `npm` registry you can specify that inside the `package.json`’s [`publishConfig`](https://docs.npmjs.com/files/package.json#publishconfig) field. | ||||||
| 
 |  | ||||||
| 3. If you want to use another dist-tag for your publishes than `'latest'` you can specify that inside the `package.json`’s [`publishConfig`](https://docs.npmjs.com/files/package.json#publishconfig) field. | 3. If you want to use another dist-tag for your publishes than `'latest'` you can specify that inside the `package.json`’s [`publishConfig`](https://docs.npmjs.com/files/package.json#publishconfig) field. | ||||||
| 
 | 
 | ||||||
| ## Plugins | ## Plugins | ||||||
| 
 | 
 | ||||||
| There are numerous steps where you can customize `semantic-release`’s behaviour using plugins. A plugin is a regular [option](#options), but passed inside the `release` block of `package.json`: | There are numerous steps where you can customize `semantic-release`’s behavior using plugins. A plugin is a regular [option](#options), but passed inside the `release` block of `package.json`: | ||||||
| 
 | 
 | ||||||
| ```json | ```json | ||||||
| { | { | ||||||
| @ -190,12 +188,13 @@ There are numerous steps where you can customize `semantic-release`’s behaviou | |||||||
|     "verifyConditions": { |     "verifyConditions": { | ||||||
|       "path": "./path/to/a/module", |       "path": "./path/to/a/module", | ||||||
|       "additional": "config" |       "additional": "config" | ||||||
|     } |     }, | ||||||
|  |     "globalPluginOptions": "globalConfig" | ||||||
|   } |   } | ||||||
| } | } | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| ``` | ```bash | ||||||
| semantic-release --analyze-commits="npm-module-name" | semantic-release --analyze-commits="npm-module-name" | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| @ -205,9 +204,9 @@ A plugin itself is an async function that always receives three arguments. | |||||||
| module.exports = function (pluginConfig, config, callback) {} | module.exports = function (pluginConfig, config, callback) {} | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| - `pluginConfig`: If the user of your plugin specifies additional plugin config in the `package.json` (see the `verifyConditions` example above) then it’s this object. | - `pluginConfig`: If the user of your plugin specifies additional plugin config in the `package.json` (see the `verifyConditions` example above) then it’s this object. Options defined directly under `release` will be passed to each plugins. Options defined within a plugin will passed only to that instance of the plugin. | ||||||
| - `config`: A config object containing a lot of information to act upon. | - `config`: A config object containing a lot of information to act upon. | ||||||
|   - `options`: `semantic-release` options like `debug`, or `branch` |   - `options`: `semantic-release` options like `repositoryUrl`, or `branch` | ||||||
|   - For certain plugins the `config` object contains even more information. See below. |   - For certain plugins the `config` object contains even more information. See below. | ||||||
| 
 | 
 | ||||||
| ### `analyzeCommits` | ### `analyzeCommits` | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| const {isArray, isObject} = require('lodash'); | const {isArray, isObject, omit} = require('lodash'); | ||||||
| const DEFINITIONS = require('./definitions'); | const DEFINITIONS = require('./definitions'); | ||||||
| const pipeline = require('./pipeline'); | const pipeline = require('./pipeline'); | ||||||
| const normalize = require('./normalize'); | const normalize = require('./normalize'); | ||||||
| @ -20,9 +20,11 @@ module.exports = (options, logger) => | |||||||
|       pluginConfs = def; |       pluginConfs = def; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     const globalOpts = omit(options, Object.keys(DEFINITIONS)); | ||||||
|  | 
 | ||||||
|     plugins[pluginType] = isArray(pluginConfs) |     plugins[pluginType] = isArray(pluginConfs) | ||||||
|       ? pipeline(pluginConfs.map(conf => normalize(pluginType, conf, logger, output))) |       ? pipeline(pluginConfs.map(conf => normalize(pluginType, globalOpts, conf, logger, output))) | ||||||
|       : normalize(pluginType, pluginConfs, logger, output); |       : normalize(pluginType, globalOpts, pluginConfs, logger, output); | ||||||
| 
 | 
 | ||||||
|     return plugins; |     return plugins; | ||||||
|   }, {}); |   }, {}); | ||||||
|  | |||||||
| @ -2,21 +2,22 @@ const {inspect} = require('util'); | |||||||
| const {isString, isObject, isFunction, noop, cloneDeep} = require('lodash'); | const {isString, isObject, isFunction, noop, cloneDeep} = require('lodash'); | ||||||
| const importFrom = require('import-from'); | const importFrom = require('import-from'); | ||||||
| 
 | 
 | ||||||
| module.exports = (pluginType, pluginConfig, logger, validator) => { | module.exports = (pluginType, globalOpts, pluginOpts, logger, validator) => { | ||||||
|   if (!pluginConfig) { |   if (!pluginOpts) { | ||||||
|     return noop; |     return noop; | ||||||
|   } |   } | ||||||
|   const {path, ...config} = isString(pluginConfig) || isFunction(pluginConfig) ? {path: pluginConfig} : pluginConfig; |   const {path, ...config} = isString(pluginOpts) || isFunction(pluginOpts) ? {path: pluginOpts} : pluginOpts; | ||||||
|   if (!isFunction(pluginConfig)) { |   if (!isFunction(pluginOpts)) { | ||||||
|     logger.log('Load plugin %s from %s', pluginType, path); |     logger.log('Load plugin %s from %s', pluginType, path); | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|   const plugin = isFunction(path) ? path : importFrom.silent(__dirname, path) || importFrom(process.cwd(), path); |   const plugin = isFunction(path) ? path : importFrom.silent(__dirname, path) || importFrom(process.cwd(), path); | ||||||
| 
 | 
 | ||||||
|   let func; |   let func; | ||||||
|   if (isFunction(plugin)) { |   if (isFunction(plugin)) { | ||||||
|     func = plugin.bind(null, cloneDeep(config)); |     func = plugin.bind(null, cloneDeep({...globalOpts, ...config})); | ||||||
|   } else if (isObject(plugin) && plugin[pluginType] && isFunction(plugin[pluginType])) { |   } else if (isObject(plugin) && plugin[pluginType] && isFunction(plugin[pluginType])) { | ||||||
|     func = plugin[pluginType].bind(null, cloneDeep(config)); |     func = plugin[pluginType].bind(null, cloneDeep({...globalOpts, ...config})); | ||||||
|   } else { |   } else { | ||||||
|     throw new Error( |     throw new Error( | ||||||
|       `The ${pluginType} plugin must be a function, or an object with a function in the property ${pluginType}.` |       `The ${pluginType} plugin must be a function, or an object with a function in the property ${pluginType}.` | ||||||
|  | |||||||
| @ -50,9 +50,9 @@ test.serial('Plugins are called with expected values', async t => { | |||||||
|   const generateNotes = stub().resolves(notes); |   const generateNotes = stub().resolves(notes); | ||||||
|   const publish = stub().resolves(); |   const publish = stub().resolves(); | ||||||
| 
 | 
 | ||||||
|  |   const config = {branch: 'master', repositoryUrl: 'git@hostname.com:owner/module.git', globalOpt: 'global'}; | ||||||
|   const options = { |   const options = { | ||||||
|     branch: 'master', |     ...config, | ||||||
|     repositoryUrl: 'git@hostname.com:owner/module.git', |  | ||||||
|     verifyConditions: [verifyConditions1, verifyConditions2], |     verifyConditions: [verifyConditions1, verifyConditions2], | ||||||
|     getLastRelease, |     getLastRelease, | ||||||
|     analyzeCommits, |     analyzeCommits, | ||||||
| @ -64,14 +64,17 @@ test.serial('Plugins are called with expected values', async t => { | |||||||
|   await t.context.semanticRelease(options); |   await t.context.semanticRelease(options); | ||||||
| 
 | 
 | ||||||
|   t.is(verifyConditions1.callCount, 1); |   t.is(verifyConditions1.callCount, 1); | ||||||
|  |   t.deepEqual(verifyConditions1.args[0][0], config); | ||||||
|   t.deepEqual(verifyConditions1.args[0][1], {options, logger: t.context.logger}); |   t.deepEqual(verifyConditions1.args[0][1], {options, logger: t.context.logger}); | ||||||
|   t.is(verifyConditions2.callCount, 1); |   t.is(verifyConditions2.callCount, 1); | ||||||
|   t.deepEqual(verifyConditions2.args[0][1], {options, logger: t.context.logger}); |   t.deepEqual(verifyConditions2.args[0][1], {options, logger: t.context.logger}); | ||||||
| 
 | 
 | ||||||
|   t.is(getLastRelease.callCount, 1); |   t.is(getLastRelease.callCount, 1); | ||||||
|  |   t.deepEqual(getLastRelease.args[0][0], config); | ||||||
|   t.deepEqual(getLastRelease.args[0][1], {options, logger: t.context.logger}); |   t.deepEqual(getLastRelease.args[0][1], {options, logger: t.context.logger}); | ||||||
| 
 | 
 | ||||||
|   t.is(analyzeCommits.callCount, 1); |   t.is(analyzeCommits.callCount, 1); | ||||||
|  |   t.deepEqual(analyzeCommits.args[0][0], config); | ||||||
|   t.deepEqual(analyzeCommits.args[0][1].options, options); |   t.deepEqual(analyzeCommits.args[0][1].options, options); | ||||||
|   t.deepEqual(analyzeCommits.args[0][1].logger, t.context.logger); |   t.deepEqual(analyzeCommits.args[0][1].logger, t.context.logger); | ||||||
|   t.deepEqual(analyzeCommits.args[0][1].lastRelease, lastRelease); |   t.deepEqual(analyzeCommits.args[0][1].lastRelease, lastRelease); | ||||||
| @ -79,6 +82,7 @@ test.serial('Plugins are called with expected values', async t => { | |||||||
|   t.deepEqual(analyzeCommits.args[0][1].commits[0].message, commits[0].message); |   t.deepEqual(analyzeCommits.args[0][1].commits[0].message, commits[0].message); | ||||||
| 
 | 
 | ||||||
|   t.is(verifyRelease.callCount, 1); |   t.is(verifyRelease.callCount, 1); | ||||||
|  |   t.deepEqual(verifyRelease.args[0][0], config); | ||||||
|   t.deepEqual(verifyRelease.args[0][1].options, options); |   t.deepEqual(verifyRelease.args[0][1].options, options); | ||||||
|   t.deepEqual(verifyRelease.args[0][1].logger, t.context.logger); |   t.deepEqual(verifyRelease.args[0][1].logger, t.context.logger); | ||||||
|   t.deepEqual(verifyRelease.args[0][1].lastRelease, lastRelease); |   t.deepEqual(verifyRelease.args[0][1].lastRelease, lastRelease); | ||||||
| @ -87,6 +91,7 @@ test.serial('Plugins are called with expected values', async t => { | |||||||
|   t.deepEqual(verifyRelease.args[0][1].nextRelease, nextRelease); |   t.deepEqual(verifyRelease.args[0][1].nextRelease, nextRelease); | ||||||
| 
 | 
 | ||||||
|   t.is(generateNotes.callCount, 1); |   t.is(generateNotes.callCount, 1); | ||||||
|  |   t.deepEqual(generateNotes.args[0][0], config); | ||||||
|   t.deepEqual(generateNotes.args[0][1].options, options); |   t.deepEqual(generateNotes.args[0][1].options, options); | ||||||
|   t.deepEqual(generateNotes.args[0][1].logger, t.context.logger); |   t.deepEqual(generateNotes.args[0][1].logger, t.context.logger); | ||||||
|   t.deepEqual(generateNotes.args[0][1].lastRelease, lastRelease); |   t.deepEqual(generateNotes.args[0][1].lastRelease, lastRelease); | ||||||
| @ -95,6 +100,7 @@ test.serial('Plugins are called with expected values', async t => { | |||||||
|   t.deepEqual(generateNotes.args[0][1].nextRelease, nextRelease); |   t.deepEqual(generateNotes.args[0][1].nextRelease, nextRelease); | ||||||
| 
 | 
 | ||||||
|   t.is(publish.callCount, 1); |   t.is(publish.callCount, 1); | ||||||
|  |   t.deepEqual(publish.args[0][0], config); | ||||||
|   t.deepEqual(publish.args[0][1].options, options); |   t.deepEqual(publish.args[0][1].options, options); | ||||||
|   t.deepEqual(publish.args[0][1].logger, t.context.logger); |   t.deepEqual(publish.args[0][1].logger, t.context.logger); | ||||||
|   t.deepEqual(publish.args[0][1].lastRelease, lastRelease); |   t.deepEqual(publish.args[0][1].lastRelease, lastRelease); | ||||||
|  | |||||||
| @ -10,27 +10,27 @@ test.beforeEach(t => { | |||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| test('Normalize and load plugin from string', t => { | test('Normalize and load plugin from string', t => { | ||||||
|   const plugin = normalize('verifyConditions', './test/fixtures/plugin-noop', t.context.logger); |   const plugin = normalize('verifyConditions', {}, './test/fixtures/plugin-noop', t.context.logger); | ||||||
| 
 | 
 | ||||||
|   t.is(typeof plugin, 'function'); |   t.is(typeof plugin, 'function'); | ||||||
|   t.deepEqual(t.context.log.args[0], ['Load plugin %s from %s', 'verifyConditions', './test/fixtures/plugin-noop']); |   t.deepEqual(t.context.log.args[0], ['Load plugin %s from %s', 'verifyConditions', './test/fixtures/plugin-noop']); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| test('Normalize and load plugin from object', t => { | test('Normalize and load plugin from object', t => { | ||||||
|   const plugin = normalize('publish', {path: './test/fixtures/plugin-noop'}, t.context.logger); |   const plugin = normalize('publish', {}, {path: './test/fixtures/plugin-noop'}, t.context.logger); | ||||||
| 
 | 
 | ||||||
|   t.is(typeof plugin, 'function'); |   t.is(typeof plugin, 'function'); | ||||||
|   t.deepEqual(t.context.log.args[0], ['Load plugin %s from %s', 'publish', './test/fixtures/plugin-noop']); |   t.deepEqual(t.context.log.args[0], ['Load plugin %s from %s', 'publish', './test/fixtures/plugin-noop']); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| test('Normalize and load plugin from function', t => { | test('Normalize and load plugin from function', t => { | ||||||
|   const plugin = normalize('', () => {}, t.context.logger); |   const plugin = normalize('', {}, () => {}, t.context.logger); | ||||||
| 
 | 
 | ||||||
|   t.is(typeof plugin, 'function'); |   t.is(typeof plugin, 'function'); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| test('Normalize and load plugin that retuns multiple functions', t => { | test('Normalize and load plugin that retuns multiple functions', t => { | ||||||
|   const plugin = normalize('verifyConditions', './test/fixtures/multi-plugin', t.context.logger); |   const plugin = normalize('verifyConditions', {}, './test/fixtures/multi-plugin', t.context.logger); | ||||||
| 
 | 
 | ||||||
|   t.is(typeof plugin, 'function'); |   t.is(typeof plugin, 'function'); | ||||||
|   t.deepEqual(t.context.log.args[0], ['Load plugin %s from %s', 'verifyConditions', './test/fixtures/multi-plugin']); |   t.deepEqual(t.context.log.args[0], ['Load plugin %s from %s', 'verifyConditions', './test/fixtures/multi-plugin']); | ||||||
| @ -38,7 +38,7 @@ test('Normalize and load plugin that retuns multiple functions', t => { | |||||||
| 
 | 
 | ||||||
| test('Wrap plugin in a function that validate the output of the plugin', async t => { | test('Wrap plugin in a function that validate the output of the plugin', async t => { | ||||||
|   const pluginFunction = stub().resolves(1); |   const pluginFunction = stub().resolves(1); | ||||||
|   const plugin = normalize('', pluginFunction, t.context.logger, { |   const plugin = normalize('', {}, pluginFunction, t.context.logger, { | ||||||
|     validator: output => output === 1, |     validator: output => output === 1, | ||||||
|     message: 'The output must be 1.', |     message: 'The output must be 1.', | ||||||
|   }); |   }); | ||||||
| @ -50,13 +50,14 @@ test('Wrap plugin in a function that validate the output of the plugin', async t | |||||||
|   t.is(error.message, 'The output must be 1. Received: 2'); |   t.is(error.message, 'The output must be 1. Received: 2'); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| test('Plugin is called with "pluginConfig" (omitting "path") and input', async t => { | test('Plugin is called with "pluginConfig" (omitting "path", adding global config) and input', async t => { | ||||||
|   const pluginFunction = stub().resolves(); |   const pluginFunction = stub().resolves(); | ||||||
|   const conf = {path: pluginFunction, conf: 'confValue'}; |   const conf = {path: pluginFunction, conf: 'confValue'}; | ||||||
|   const plugin = normalize('', conf, t.context.logger); |   const globalConf = {global: 'globalValue'}; | ||||||
|  |   const plugin = normalize('', globalConf, conf, t.context.logger); | ||||||
|   await plugin('param'); |   await plugin('param'); | ||||||
| 
 | 
 | ||||||
|   t.true(pluginFunction.calledWith({conf: 'confValue'}, 'param')); |   t.true(pluginFunction.calledWith({conf: 'confValue', global: 'globalValue'}, 'param')); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| test('Prevent plugins to modify "pluginConfig"', async t => { | test('Prevent plugins to modify "pluginConfig"', async t => { | ||||||
| @ -64,10 +65,12 @@ test('Prevent plugins to modify "pluginConfig"', async t => { | |||||||
|     pluginConfig.conf.subConf = 'otherConf'; |     pluginConfig.conf.subConf = 'otherConf'; | ||||||
|   }); |   }); | ||||||
|   const conf = {path: pluginFunction, conf: {subConf: 'originalConf'}}; |   const conf = {path: pluginFunction, conf: {subConf: 'originalConf'}}; | ||||||
|   const plugin = normalize('', conf, t.context.logger); |   const globalConf = {globalConf: {globalSubConf: 'originalGlobalConf'}}; | ||||||
|  |   const plugin = normalize('', globalConf, conf, t.context.logger); | ||||||
|   await plugin(); |   await plugin(); | ||||||
| 
 | 
 | ||||||
|   t.is(conf.conf.subConf, 'originalConf'); |   t.is(conf.conf.subConf, 'originalConf'); | ||||||
|  |   t.is(globalConf.globalConf.globalSubConf, 'originalGlobalConf'); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| test('Prevent plugins to modify its input', async t => { | test('Prevent plugins to modify its input', async t => { | ||||||
| @ -75,7 +78,7 @@ test('Prevent plugins to modify its input', async t => { | |||||||
|     options.param.subParam = 'otherParam'; |     options.param.subParam = 'otherParam'; | ||||||
|   }); |   }); | ||||||
|   const input = {param: {subParam: 'originalSubParam'}}; |   const input = {param: {subParam: 'originalSubParam'}}; | ||||||
|   const plugin = normalize('', pluginFunction, t.context.logger); |   const plugin = normalize('', {}, pluginFunction, t.context.logger); | ||||||
|   await plugin(input); |   await plugin(input); | ||||||
| 
 | 
 | ||||||
|   t.is(input.param.subParam, 'originalSubParam'); |   t.is(input.param.subParam, 'originalSubParam'); | ||||||
| @ -89,7 +92,7 @@ test('Return noop if the plugin is not defined', t => { | |||||||
| 
 | 
 | ||||||
| test('Always pass a defined "pluginConfig" for plugin defined with string', async t => { | test('Always pass a defined "pluginConfig" for plugin defined with string', async t => { | ||||||
|   // Call the normalize function with the path of a plugin that returns its config
 |   // Call the normalize function with the path of a plugin that returns its config
 | ||||||
|   const plugin = normalize('', './test/fixtures/plugin-result-config', t.context.logger); |   const plugin = normalize('', {}, './test/fixtures/plugin-result-config', t.context.logger); | ||||||
|   const pluginResult = await plugin(); |   const pluginResult = await plugin(); | ||||||
| 
 | 
 | ||||||
|   t.deepEqual(pluginResult.pluginConfig, {}); |   t.deepEqual(pluginResult.pluginConfig, {}); | ||||||
| @ -97,14 +100,17 @@ test('Always pass a defined "pluginConfig" for plugin defined with string', asyn | |||||||
| 
 | 
 | ||||||
| test('Always pass a defined "pluginConfig" for plugin defined with path', async t => { | test('Always pass a defined "pluginConfig" for plugin defined with path', async t => { | ||||||
|   // Call the normalize function with the path of a plugin that returns its config
 |   // Call the normalize function with the path of a plugin that returns its config
 | ||||||
|   const plugin = normalize('', {path: './test/fixtures/plugin-result-config'}, t.context.logger); |   const plugin = normalize('', {}, {path: './test/fixtures/plugin-result-config'}, t.context.logger); | ||||||
|   const pluginResult = await plugin(); |   const pluginResult = await plugin(); | ||||||
| 
 | 
 | ||||||
|   t.deepEqual(pluginResult.pluginConfig, {}); |   t.deepEqual(pluginResult.pluginConfig, {}); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| test('Throws an error if the plugin return an object without the expected plugin function', t => { | test('Throws an error if the plugin return an object without the expected plugin function', t => { | ||||||
|   const error = t.throws(() => normalize('inexistantPlugin', './test/fixtures/multi-plugin', t.context.logger), Error); |   const error = t.throws( | ||||||
|  |     () => normalize('inexistantPlugin', {}, './test/fixtures/multi-plugin', t.context.logger), | ||||||
|  |     Error | ||||||
|  |   ); | ||||||
| 
 | 
 | ||||||
|   t.is( |   t.is( | ||||||
|     error.message, |     error.message, | ||||||
|  | |||||||
| @ -48,6 +48,21 @@ test('Use default when only options are passed for a single plugin', t => { | |||||||
|   t.is(typeof plugins.analyzeCommits, 'function'); |   t.is(typeof plugins.analyzeCommits, 'function'); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
|  | test('Merge global options with plugin options', async t => { | ||||||
|  |   const plugins = getPlugins( | ||||||
|  |     { | ||||||
|  |       globalOpt: 'global', | ||||||
|  |       otherOpt: 'globally-defined', | ||||||
|  |       getLastRelease: {path: './test/fixtures/plugin-result-config', localOpt: 'local', otherOpt: 'locally-defined'}, | ||||||
|  |     }, | ||||||
|  |     t.context.logger | ||||||
|  |   ); | ||||||
|  | 
 | ||||||
|  |   const result = await plugins.getLastRelease(); | ||||||
|  | 
 | ||||||
|  |   t.deepEqual(result.pluginConfig, {localOpt: 'local', globalOpt: 'global', otherOpt: 'locally-defined'}); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
| test('Throw an error if plugin configuration is missing a path for plugin pipeline', t => { | test('Throw an error if plugin configuration is missing a path for plugin pipeline', t => { | ||||||
|   const error = t.throws(() => getPlugins({verifyConditions: {}}, t.context.logger), Error); |   const error = t.throws(() => getPlugins({verifyConditions: {}}, t.context.logger), Error); | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user