- Add a new plugin type: `publish` - Add support for multi-plugin. A plugin module can now return an object with a property for each plugin type - Uses by default [npm](https://github.com/semantic-release/npm) and [github](https://github.com/semantic-release/github) in addition of Travis for the verify condition plugin - Uses by default [npm](https://github.com/semantic-release/npm) and [github](https://github.com/semantic-release/github) for the publish plugin - `gitTag` if one can be found is passed to `generateNotes` for both `lastRelease` and `nextRelease` - `semantic-release` now verifies the plugin configuration (in the `release` property of `package.json`) and throws an error if it's invalid - `semantic-release` now verifies each plugin output and will throw an error if a plugin returns an unexpected value. BREAKING CHANGE: `githubToken`, `githubUrl` and `githubApiPathPrefix` have to be set at the [github](https://github.com/semantic-release/github) plugin level. They can be set via `GH_TOKEN`, `GH_URL` and `GH_PREFIX` environment variables. BREAKING CHANGE: the `npm` parameter is not passed to any plugin anymore. Each plugin have to read `.npmrc` if they needs to (with https://github.com/kevva/npm-conf for example).
117 lines
4.0 KiB
JavaScript
117 lines
4.0 KiB
JavaScript
import {callbackify} from 'util';
|
|
import test from 'ava';
|
|
import {noop} from 'lodash';
|
|
import {stub, match} from 'sinon';
|
|
import normalize from '../../lib/plugins/normalize';
|
|
|
|
test.beforeEach(t => {
|
|
// Stub the logger functions
|
|
t.context.log = stub();
|
|
t.context.logger = {log: t.context.log};
|
|
});
|
|
|
|
test('Normalize and load plugin from string', t => {
|
|
const plugin = normalize('', './test/fixtures/plugin-noop', t.context.logger);
|
|
|
|
t.is(typeof plugin, 'function');
|
|
t.true(t.context.log.calledWith(match.string, './test/fixtures/plugin-noop'));
|
|
});
|
|
|
|
test('Normalize and load plugin from object', t => {
|
|
const plugin = normalize('', {path: './test/fixtures/plugin-noop'}, t.context.logger);
|
|
|
|
t.is(typeof plugin, 'function');
|
|
t.true(t.context.log.calledWith(match.string, './test/fixtures/plugin-noop'));
|
|
});
|
|
|
|
test('Normalize and load plugin from function', t => {
|
|
const plugin = normalize('', () => {}, t.context.logger);
|
|
|
|
t.is(typeof plugin, 'function');
|
|
});
|
|
|
|
test('Normalize and load plugin that retuns multiple functions', t => {
|
|
const plugin = normalize('verifyConditions', './test/fixtures/multi-plugin', t.context.logger);
|
|
|
|
t.is(typeof plugin, 'function');
|
|
t.true(t.context.log.calledWith(match.string, './test/fixtures/multi-plugin'));
|
|
});
|
|
|
|
test('Wrap plugin in a function that validate the output of the plugin', async t => {
|
|
const pluginFunction = stub().resolves(1);
|
|
const plugin = normalize('', callbackify(pluginFunction), t.context.logger, {
|
|
validator: output => output === 1,
|
|
message: 'The output must be 1',
|
|
});
|
|
|
|
await t.notThrows(plugin());
|
|
|
|
pluginFunction.resolves(2);
|
|
const error = await t.throws(plugin());
|
|
t.is(error.message, 'The output must be 1. Received: 2');
|
|
});
|
|
|
|
test('Plugin is called with "pluginConfig" (omitting "path") and input', async t => {
|
|
const pluginFunction = stub().resolves();
|
|
const conf = {path: callbackify(pluginFunction), conf: 'confValue'};
|
|
const plugin = normalize('', conf, t.context.logger);
|
|
await plugin('param');
|
|
|
|
t.true(pluginFunction.calledWith({conf: 'confValue'}, 'param'));
|
|
});
|
|
|
|
test('Prevent plugins to modify "pluginConfig"', async t => {
|
|
const pluginFunction = stub().callsFake((pluginConfig, options, cb) => {
|
|
pluginConfig.conf.subConf = 'otherConf';
|
|
cb();
|
|
});
|
|
const conf = {path: pluginFunction, conf: {subConf: 'originalConf'}};
|
|
const plugin = normalize('', conf, t.context.logger);
|
|
await plugin();
|
|
|
|
t.is(conf.conf.subConf, 'originalConf');
|
|
});
|
|
|
|
test('Prevent plugins to modify its input', async t => {
|
|
const pluginFunction = stub().callsFake((pluginConfig, options, cb) => {
|
|
options.param.subParam = 'otherParam';
|
|
cb();
|
|
});
|
|
const input = {param: {subParam: 'originalSubParam'}};
|
|
const plugin = normalize('', pluginFunction, t.context.logger);
|
|
await plugin(input);
|
|
|
|
t.is(input.param.subParam, 'originalSubParam');
|
|
});
|
|
|
|
test('Return noop if the plugin is not defined', async t => {
|
|
const plugin = normalize();
|
|
|
|
t.is(plugin, noop);
|
|
});
|
|
|
|
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
|
|
const plugin = normalize('', './test/fixtures/plugin-result-config', t.context.logger);
|
|
const pluginResult = await plugin();
|
|
|
|
t.deepEqual(pluginResult.pluginConfig, {});
|
|
});
|
|
|
|
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
|
|
const plugin = normalize('', {path: './test/fixtures/plugin-result-config'}, t.context.logger);
|
|
const pluginResult = await plugin();
|
|
|
|
t.deepEqual(pluginResult.pluginConfig, {});
|
|
});
|
|
|
|
test('Throws an error if the plugin return an object without the expected plugin function', async t => {
|
|
const error = t.throws(() => normalize('inexistantPlugin', './test/fixtures/multi-plugin', t.context.logger));
|
|
|
|
t.is(
|
|
error.message,
|
|
'The inexistantPlugin plugin must be a function, or an object with a function in the property inexistantPlugin.'
|
|
);
|
|
});
|