- Allow to configure multiple branches to release from - Allow to define a distribution channel associated with each branch - Manage the availability on distribution channels based on git merges - Support regular releases, maintenance releases and pre-releases - Add the `addChannel` plugin step to make an existing release available on a different distribution channel BREAKING CHANGE: the `branch` option has been removed in favor of `branches` The new `branches` option expect either an Array or a single branch definition. To migrate your configuration: - If you want to publish package from multiple branches, please the configuration documentation - If you use the default configuration and want to publish only from `master`: nothing to change - If you use the `branch` configuration and want to publish only from one branch: replace `branch` by `branches` (`"branch": "my-release-branch"` => `"branches": "my-release-branch"`)
279 lines
8.9 KiB
JavaScript
279 lines
8.9 KiB
JavaScript
import test from 'ava';
|
|
import {noop} from 'lodash';
|
|
import {stub} from 'sinon';
|
|
import normalize from '../../lib/plugins/normalize';
|
|
|
|
const cwd = process.cwd();
|
|
|
|
test.beforeEach(t => {
|
|
// Stub the logger functions
|
|
t.context.log = stub();
|
|
t.context.error = stub();
|
|
t.context.success = stub();
|
|
t.context.stderr = {write: stub()};
|
|
t.context.logger = {
|
|
log: t.context.log,
|
|
error: t.context.error,
|
|
success: t.context.success,
|
|
scope: () => t.context.logger,
|
|
};
|
|
});
|
|
|
|
test('Normalize and load plugin from string', t => {
|
|
const plugin = normalize(
|
|
{cwd, options: {}, logger: t.context.logger},
|
|
'verifyConditions',
|
|
'./test/fixtures/plugin-noop',
|
|
{}
|
|
);
|
|
|
|
t.is(plugin.pluginName, './test/fixtures/plugin-noop');
|
|
t.is(typeof plugin, 'function');
|
|
t.deepEqual(t.context.success.args[0], ['Loaded plugin "verifyConditions" from "./test/fixtures/plugin-noop"']);
|
|
});
|
|
|
|
test('Normalize and load plugin from object', t => {
|
|
const plugin = normalize(
|
|
{cwd, options: {}, logger: t.context.logger},
|
|
'publish',
|
|
{path: './test/fixtures/plugin-noop'},
|
|
{}
|
|
);
|
|
|
|
t.is(plugin.pluginName, './test/fixtures/plugin-noop');
|
|
t.is(typeof plugin, 'function');
|
|
t.deepEqual(t.context.success.args[0], ['Loaded plugin "publish" from "./test/fixtures/plugin-noop"']);
|
|
});
|
|
|
|
test('Normalize and load plugin from a base file path', t => {
|
|
const plugin = normalize({cwd, options: {}, logger: t.context.logger}, 'verifyConditions', './plugin-noop', {
|
|
'./plugin-noop': './test/fixtures',
|
|
});
|
|
|
|
t.is(plugin.pluginName, './plugin-noop');
|
|
t.is(typeof plugin, 'function');
|
|
t.deepEqual(t.context.success.args[0], [
|
|
'Loaded plugin "verifyConditions" from "./plugin-noop" in shareable config "./test/fixtures"',
|
|
]);
|
|
});
|
|
|
|
test('Wrap plugin in a function that add the "pluginName" to the error"', async t => {
|
|
const plugin = normalize({cwd, options: {}, logger: t.context.logger}, 'verifyConditions', './plugin-error', {
|
|
'./plugin-error': './test/fixtures',
|
|
});
|
|
|
|
const error = await t.throws(plugin({options: {}}));
|
|
|
|
t.is(error.pluginName, './plugin-error');
|
|
});
|
|
|
|
test('Wrap plugin in a function that add the "pluginName" to multiple errors"', async t => {
|
|
const plugin = normalize({cwd, options: {}, logger: t.context.logger}, 'verifyConditions', './plugin-errors', {
|
|
'./plugin-errors': './test/fixtures',
|
|
});
|
|
|
|
const errors = [...(await t.throws(plugin({options: {}})))];
|
|
for (const error of errors) {
|
|
t.is(error.pluginName, './plugin-errors');
|
|
}
|
|
});
|
|
|
|
test('Normalize and load plugin from function', t => {
|
|
const pluginFunction = () => {};
|
|
const plugin = normalize({cwd, options: {}, logger: t.context.logger}, '', pluginFunction, {});
|
|
|
|
t.is(plugin.pluginName, '[Function: pluginFunction]');
|
|
t.is(typeof plugin, 'function');
|
|
});
|
|
|
|
test('Normalize and load plugin that retuns multiple functions', t => {
|
|
const plugin = normalize(
|
|
{cwd, options: {}, logger: t.context.logger},
|
|
'verifyConditions',
|
|
'./test/fixtures/multi-plugin',
|
|
{}
|
|
);
|
|
|
|
t.is(typeof plugin, 'function');
|
|
t.deepEqual(t.context.success.args[0], ['Loaded plugin "verifyConditions" from "./test/fixtures/multi-plugin"']);
|
|
});
|
|
|
|
test('Wrap "analyzeCommits" plugin in a function that validate the output of the plugin', async t => {
|
|
const analyzeCommits = stub().resolves(2);
|
|
const plugin = normalize(
|
|
{cwd, options: {}, stderr: t.context.stderr, logger: t.context.logger},
|
|
'analyzeCommits',
|
|
analyzeCommits,
|
|
{}
|
|
);
|
|
|
|
const error = await t.throws(plugin({options: {}}));
|
|
|
|
t.is(error.code, 'EANALYZECOMMITSOUTPUT');
|
|
t.is(error.name, 'SemanticReleaseError');
|
|
t.truthy(error.message);
|
|
t.truthy(error.details);
|
|
t.regex(error.details, /2/);
|
|
});
|
|
|
|
test('Wrap "generateNotes" plugin in a function that validate the output of the plugin', async t => {
|
|
const generateNotes = stub().resolves(2);
|
|
const plugin = normalize(
|
|
{cwd, options: {}, stderr: t.context.stderr, logger: t.context.logger},
|
|
'generateNotes',
|
|
generateNotes,
|
|
{}
|
|
);
|
|
|
|
const error = await t.throws(plugin({options: {}}));
|
|
|
|
t.is(error.code, 'EGENERATENOTESOUTPUT');
|
|
t.is(error.name, 'SemanticReleaseError');
|
|
t.truthy(error.message);
|
|
t.truthy(error.details);
|
|
t.regex(error.details, /2/);
|
|
});
|
|
|
|
test('Wrap "publish" plugin in a function that validate the output of the plugin', async t => {
|
|
const publish = stub().resolves(2);
|
|
const plugin = normalize(
|
|
{cwd, options: {}, stderr: t.context.stderr, logger: t.context.logger},
|
|
'publish',
|
|
publish,
|
|
{}
|
|
);
|
|
|
|
const error = await t.throws(plugin({options: {}}));
|
|
|
|
t.is(error.code, 'EPUBLISHOUTPUT');
|
|
t.is(error.name, 'SemanticReleaseError');
|
|
t.truthy(error.message);
|
|
t.truthy(error.details);
|
|
t.regex(error.details, /2/);
|
|
});
|
|
|
|
test('Wrap "addChannel" plugin in a function that validate the output of the plugin', async t => {
|
|
const addChannel = stub().resolves(2);
|
|
const plugin = normalize(
|
|
{cwd, options: {}, stderr: t.context.stderr, logger: t.context.logger},
|
|
'addChannel',
|
|
addChannel,
|
|
{}
|
|
);
|
|
|
|
const error = await t.throws(plugin({options: {}}));
|
|
|
|
t.is(error.code, 'EADDCHANNELOUTPUT');
|
|
t.is(error.name, 'SemanticReleaseError');
|
|
t.truthy(error.message);
|
|
t.truthy(error.details);
|
|
t.regex(error.details, /2/);
|
|
});
|
|
|
|
test('Plugin is called with "pluginConfig" (with object definition) and input', async t => {
|
|
const pluginFunction = stub().resolves();
|
|
const pluginConf = {path: pluginFunction, conf: 'confValue'};
|
|
const options = {global: 'globalValue'};
|
|
const plugin = normalize({cwd, options, logger: t.context.logger}, '', pluginConf, {});
|
|
await plugin({options: {}, param: 'param'});
|
|
|
|
t.true(
|
|
pluginFunction.calledWithMatch(
|
|
{conf: 'confValue', global: 'globalValue'},
|
|
{param: 'param', logger: t.context.logger}
|
|
)
|
|
);
|
|
});
|
|
|
|
test('Plugin is called with "pluginConfig" (with array definition) and input', async t => {
|
|
const pluginFunction = stub().resolves();
|
|
const pluginConf = [pluginFunction, {conf: 'confValue'}];
|
|
const options = {global: 'globalValue'};
|
|
const plugin = normalize({cwd, options, logger: t.context.logger}, '', pluginConf, {});
|
|
await plugin({options: {}, param: 'param'});
|
|
|
|
t.true(
|
|
pluginFunction.calledWithMatch(
|
|
{conf: 'confValue', global: 'globalValue'},
|
|
{param: 'param', logger: t.context.logger}
|
|
)
|
|
);
|
|
});
|
|
|
|
test('Prevent plugins to modify "pluginConfig"', async t => {
|
|
const pluginFunction = stub().callsFake(pluginConfig => {
|
|
pluginConfig.conf.subConf = 'otherConf';
|
|
});
|
|
const pluginConf = {path: pluginFunction, conf: {subConf: 'originalConf'}};
|
|
const options = {globalConf: {globalSubConf: 'originalGlobalConf'}};
|
|
const plugin = normalize({cwd, options, logger: t.context.logger}, '', pluginConf, {});
|
|
await plugin({options: {}});
|
|
|
|
t.is(pluginConf.conf.subConf, 'originalConf');
|
|
t.is(options.globalConf.globalSubConf, 'originalGlobalConf');
|
|
});
|
|
|
|
test('Prevent plugins to modify its input', async t => {
|
|
const pluginFunction = stub().callsFake((pluginConfig, options) => {
|
|
options.param.subParam = 'otherParam';
|
|
});
|
|
const input = {param: {subParam: 'originalSubParam'}, options: {}};
|
|
const plugin = normalize({cwd, options: {}, logger: t.context.logger}, '', pluginFunction, {});
|
|
await plugin(input);
|
|
|
|
t.is(input.param.subParam, 'originalSubParam');
|
|
});
|
|
|
|
test('Return noop if the plugin is not defined', t => {
|
|
const plugin = normalize({cwd, options: {}, logger: t.context.logger});
|
|
|
|
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(
|
|
{cwd, options: {}, logger: t.context.logger},
|
|
'',
|
|
'./test/fixtures/plugin-result-config',
|
|
{}
|
|
);
|
|
const pluginResult = await plugin({options: {}});
|
|
|
|
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(
|
|
{cwd, options: {}, logger: t.context.logger},
|
|
'',
|
|
{path: './test/fixtures/plugin-result-config'},
|
|
{}
|
|
);
|
|
const pluginResult = await plugin({options: {}});
|
|
|
|
t.deepEqual(pluginResult.pluginConfig, {});
|
|
});
|
|
|
|
test('Throws an error if the plugin return an object without the expected plugin function', t => {
|
|
const error = t.throws(() =>
|
|
normalize({cwd, options: {}, logger: t.context.logger}, 'inexistantPlugin', './test/fixtures/multi-plugin', {})
|
|
);
|
|
|
|
t.is(error.code, 'EPLUGIN');
|
|
t.is(error.name, 'SemanticReleaseError');
|
|
t.truthy(error.message);
|
|
t.truthy(error.details);
|
|
});
|
|
|
|
test('Throws an error if the plugin is not found', t => {
|
|
const error = t.throws(
|
|
() => normalize({cwd, options: {}, logger: t.context.logger}, 'inexistantPlugin', 'non-existing-path', {}),
|
|
Error
|
|
);
|
|
|
|
t.is(error.message, "Cannot find module 'non-existing-path'");
|
|
t.is(error.code, 'MODULE_NOT_FOUND');
|
|
});
|