feat: add logs about prepare, publish, success and fail plugins skipped in dry-run mode

This commit is contained in:
Pierre Vanduynslager 2018-11-13 18:23:39 -05:00
parent 90b497464e
commit 90da6a0bc1
6 changed files with 73 additions and 52 deletions

View File

@ -24,7 +24,7 @@ async function run(context, plugins) {
const {isCi, branch: ciBranch, isPr} = envCi({env, cwd}); const {isCi, branch: ciBranch, isPr} = envCi({env, cwd});
if (!isCi && !options.dryRun && !options.noCi) { if (!isCi && !options.dryRun && !options.noCi) {
logger.log('This run was not triggered in a known CI environment, running in dry-run mode.'); logger.warn('This run was not triggered in a known CI environment, running in dry-run mode.');
options.dryRun = true; options.dryRun = true;
} else { } else {
// When running on CI, set the commits author and commiter info and prevent the `git` CLI to prompt for username/password. See #703. // When running on CI, set the commits author and commiter info and prevent the `git` CLI to prompt for username/password. See #703.
@ -52,7 +52,9 @@ async function run(context, plugins) {
); );
return false; return false;
} }
logger.success(`Run automated release from branch ${ciBranch}${options.dryRun ? ' in dry-run mode' : ''}`); logger[options.dryRun ? 'warn' : 'success'](
`Run automated release from branch ${ciBranch}${options.dryRun ? ' in dry-run mode' : ''}`
);
await verify(context); await verify(context);
@ -98,24 +100,28 @@ async function run(context, plugins) {
nextRelease.notes = await plugins.generateNotes(context); nextRelease.notes = await plugins.generateNotes(context);
if (options.dryRun) {
logger.log(`Release note for version ${nextRelease.version}:`);
if (nextRelease.notes) {
context.stdout.write(marked(nextRelease.notes));
}
} else {
await plugins.prepare(context); await plugins.prepare(context);
if (options.dryRun) {
logger.warn(`Skip ${nextRelease.gitTag} tag creation in dry-run mode`);
} else {
// Create the tag before calling the publish plugins as some require the tag to exists // Create the tag before calling the publish plugins as some require the tag to exists
await tag(nextRelease.gitTag, {cwd, env}); await tag(nextRelease.gitTag, {cwd, env});
await push(options.repositoryUrl, options.branch, {cwd, env}); await push(options.repositoryUrl, options.branch, {cwd, env});
logger.success(`Created tag ${nextRelease.gitTag}`); logger.success(`Created tag ${nextRelease.gitTag}`);
}
context.releases = await plugins.publish(context); context.releases = await plugins.publish(context);
await plugins.success(context); await plugins.success(context);
logger.success(`Published release ${nextRelease.version}`); logger.success(`Published release ${nextRelease.version}`);
if (options.dryRun) {
logger.log(`Release note for version ${nextRelease.version}:`);
if (nextRelease.notes) {
context.stdout.write(marked(nextRelease.notes));
}
} }
return pick(context, ['lastRelease', 'commits', 'nextRelease', 'releases']); return pick(context, ['lastRelease', 'commits', 'nextRelease', 'releases']);
@ -162,9 +168,7 @@ module.exports = async (opts = {}, {cwd = process.cwd(), env = process.env, stdo
unhook(); unhook();
return result; return result;
} catch (error) { } catch (error) {
if (!options.dryRun) {
await callFail(context, plugins, error); await callFail(context, plugins, error);
}
throw error; throw error;
} }
} catch (error) { } catch (error) {

View File

@ -7,11 +7,13 @@ const {RELEASE_TYPE, RELEASE_NOTES_SEPARATOR} = require('./constants');
module.exports = { module.exports = {
verifyConditions: { verifyConditions: {
required: false, required: false,
dryRun: true,
pipelineConfig: () => ({settleAll: true}), pipelineConfig: () => ({settleAll: true}),
}, },
analyzeCommits: { analyzeCommits: {
default: ['@semantic-release/commit-analyzer'], default: ['@semantic-release/commit-analyzer'],
required: true, required: true,
dryRun: true,
outputValidator: output => !output || RELEASE_TYPE.includes(output), outputValidator: output => !output || RELEASE_TYPE.includes(output),
preprocess: ({commits, ...inputs}) => ({ preprocess: ({commits, ...inputs}) => ({
...inputs, ...inputs,
@ -27,10 +29,12 @@ module.exports = {
}, },
verifyRelease: { verifyRelease: {
required: false, required: false,
dryRun: true,
pipelineConfig: () => ({settleAll: true}), pipelineConfig: () => ({settleAll: true}),
}, },
generateNotes: { generateNotes: {
required: false, required: false,
dryRun: true,
outputValidator: output => !output || isString(output), outputValidator: output => !output || isString(output),
pipelineConfig: () => ({ pipelineConfig: () => ({
getNextInput: ({nextRelease, ...context}, notes) => ({ getNextInput: ({nextRelease, ...context}, notes) => ({
@ -45,6 +49,7 @@ module.exports = {
}, },
prepare: { prepare: {
required: false, required: false,
dryRun: false,
pipelineConfig: ({generateNotes}) => ({ pipelineConfig: ({generateNotes}) => ({
getNextInput: async context => { getNextInput: async context => {
const newGitHead = await gitHead({cwd: context.cwd}); const newGitHead = await gitHead({cwd: context.cwd});
@ -61,6 +66,7 @@ module.exports = {
}, },
publish: { publish: {
required: false, required: false,
dryRun: false,
outputValidator: output => !output || isPlainObject(output), outputValidator: output => !output || isPlainObject(output),
pipelineConfig: () => ({ pipelineConfig: () => ({
// Add `nextRelease` and plugin properties to published release // Add `nextRelease` and plugin properties to published release
@ -73,11 +79,13 @@ module.exports = {
}, },
success: { success: {
required: false, required: false,
dryRun: false,
pipelineConfig: () => ({settleAll: true}), pipelineConfig: () => ({settleAll: true}),
preprocess: ({releases, env, ...inputs}) => ({...inputs, env, releases: hideSensitiveValues(env, releases)}), preprocess: ({releases, env, ...inputs}) => ({...inputs, env, releases: hideSensitiveValues(env, releases)}),
}, },
fail: { fail: {
required: false, required: false,
dryRun: false,
pipelineConfig: () => ({settleAll: true}), pipelineConfig: () => ({settleAll: true}),
preprocess: ({errors, env, ...inputs}) => ({...inputs, env, errors: hideSensitiveValues(env, errors)}), preprocess: ({errors, env, ...inputs}) => ({...inputs, env, errors: hideSensitiveValues(env, errors)}),
}, },

View File

@ -27,8 +27,9 @@ module.exports = (context, type, pluginOpt, pluginsPath) => {
} }
const validator = async input => { const validator = async input => {
const {outputValidator} = PLUGINS_DEFINITIONS[type] || {}; const {dryRun, outputValidator} = PLUGINS_DEFINITIONS[type] || {};
try { try {
if (!input.options.dryRun || dryRun) {
logger.log(`Start step "${type}" of plugin "${pluginName}"`); logger.log(`Start step "${type}" of plugin "${pluginName}"`);
const result = await func({ const result = await func({
...cloneDeep(omit(input, ['stdout', 'stderr', 'logger'])), ...cloneDeep(omit(input, ['stdout', 'stderr', 'logger'])),
@ -41,6 +42,8 @@ module.exports = (context, type, pluginOpt, pluginsPath) => {
} }
logger.success(`Completed step "${type}" of plugin "${pluginName}"`); logger.success(`Completed step "${type}" of plugin "${pluginName}"`);
return result; return result;
}
logger.warn(`Skip step "${type}" of plugin "${pluginName}" in dry-run mode`);
} catch (error) { } catch (error) {
logger.error(`Failed step "${type}" of plugin "${pluginName}"`); logger.error(`Failed step "${type}" of plugin "${pluginName}"`);
extractErrors(error).forEach(err => Object.assign(err, {pluginName})); extractErrors(error).forEach(err => Object.assign(err, {pluginName}));

View File

@ -25,10 +25,12 @@ test.beforeEach(t => {
t.context.log = spy(); t.context.log = spy();
t.context.error = spy(); t.context.error = spy();
t.context.success = spy(); t.context.success = spy();
t.context.warn = spy();
t.context.logger = { t.context.logger = {
log: t.context.log, log: t.context.log,
error: t.context.error, error: t.context.error,
success: t.context.success, success: t.context.success,
warn: t.context.warn,
scope: () => t.context.logger, scope: () => t.context.logger,
}; };
}); });
@ -476,14 +478,17 @@ test('Dry-run skips prepare, publish and success', async t => {
}) })
); );
t.not(t.context.log.args[0][0], 'This run was not triggered in a known CI environment, running in dry-run mode.'); t.not(t.context.warn.args[0][0], 'This run was not triggered in a known CI environment, running in dry-run mode.');
t.is(verifyConditions.callCount, 1); t.is(verifyConditions.callCount, 1);
t.is(analyzeCommits.callCount, 1); t.is(analyzeCommits.callCount, 1);
t.is(verifyRelease.callCount, 1); t.is(verifyRelease.callCount, 1);
t.is(generateNotes.callCount, 1); t.is(generateNotes.callCount, 1);
t.is(prepare.callCount, 0); t.is(prepare.callCount, 0);
t.true(t.context.warn.calledWith(`Skip step "prepare" of plugin "[Function: ${prepare.name}]" in dry-run mode`));
t.is(publish.callCount, 0); t.is(publish.callCount, 0);
t.true(t.context.warn.calledWith(`Skip step "publish" of plugin "[Function: ${publish.name}]" in dry-run mode`));
t.is(success.callCount, 0); t.is(success.callCount, 0);
t.true(t.context.warn.calledWith(`Skip step "success" of plugin "[Function: ${success.name}]" in dry-run mode`));
}); });
test('Dry-run skips fail', async t => { test('Dry-run skips fail', async t => {
@ -523,6 +528,7 @@ test('Dry-run skips fail', async t => {
t.true(t.context.error.calledWith('ERR1 error 1')); t.true(t.context.error.calledWith('ERR1 error 1'));
t.true(t.context.error.calledWith('ERR2 error 2')); t.true(t.context.error.calledWith('ERR2 error 2'));
t.is(fail.callCount, 0); t.is(fail.callCount, 0);
t.true(t.context.warn.calledWith(`Skip step "fail" of plugin "[Function: ${fail.name}]" in dry-run mode`));
}); });
test('Force a dry-run if not on a CI and "noCi" is not explicitly set', async t => { test('Force a dry-run if not on a CI and "noCi" is not explicitly set', async t => {
@ -573,7 +579,7 @@ test('Force a dry-run if not on a CI and "noCi" is not explicitly set', async t
}) })
); );
t.is(t.context.log.args[1][0], 'This run was not triggered in a known CI environment, running in dry-run mode.'); t.true(t.context.warn.calledWith('This run was not triggered in a known CI environment, running in dry-run mode.'));
t.is(verifyConditions.callCount, 1); t.is(verifyConditions.callCount, 1);
t.is(analyzeCommits.callCount, 1); t.is(analyzeCommits.callCount, 1);
t.is(verifyRelease.callCount, 1); t.is(verifyRelease.callCount, 1);

View File

@ -62,7 +62,7 @@ test('Wrap plugin in a function that add the "pluginName" to the error"', async
'./plugin-error': './test/fixtures', './plugin-error': './test/fixtures',
}); });
const error = await t.throws(plugin()); const error = await t.throws(plugin({options: {}}));
t.is(error.pluginName, './plugin-error'); t.is(error.pluginName, './plugin-error');
}); });
@ -72,7 +72,7 @@ test('Wrap plugin in a function that add the "pluginName" to multiple errors"',
'./plugin-errors': './test/fixtures', './plugin-errors': './test/fixtures',
}); });
const errors = [...(await t.throws(plugin()))]; const errors = [...(await t.throws(plugin({options: {}})))];
for (const error of errors) { for (const error of errors) {
t.is(error.pluginName, './plugin-errors'); t.is(error.pluginName, './plugin-errors');
} }
@ -107,7 +107,7 @@ test('Wrap "analyzeCommits" plugin in a function that validate the output of the
{} {}
); );
const error = await t.throws(plugin()); const error = await t.throws(plugin({options: {}}));
t.is(error.code, 'EANALYZECOMMITSOUTPUT'); t.is(error.code, 'EANALYZECOMMITSOUTPUT');
t.is(error.name, 'SemanticReleaseError'); t.is(error.name, 'SemanticReleaseError');
@ -125,7 +125,7 @@ test('Wrap "generateNotes" plugin in a function that validate the output of the
{} {}
); );
const error = await t.throws(plugin()); const error = await t.throws(plugin({options: {}}));
t.is(error.code, 'EGENERATENOTESOUTPUT'); t.is(error.code, 'EGENERATENOTESOUTPUT');
t.is(error.name, 'SemanticReleaseError'); t.is(error.name, 'SemanticReleaseError');
@ -143,7 +143,7 @@ test('Wrap "publish" plugin in a function that validate the output of the plugin
{} {}
); );
const error = await t.throws(plugin()); const error = await t.throws(plugin({options: {}}));
t.is(error.code, 'EPUBLISHOUTPUT'); t.is(error.code, 'EPUBLISHOUTPUT');
t.is(error.name, 'SemanticReleaseError'); t.is(error.name, 'SemanticReleaseError');
@ -157,7 +157,7 @@ test('Plugin is called with "pluginConfig" (with object definition) and input',
const pluginConf = {path: pluginFunction, conf: 'confValue'}; const pluginConf = {path: pluginFunction, conf: 'confValue'};
const options = {global: 'globalValue'}; const options = {global: 'globalValue'};
const plugin = normalize({cwd, options, logger: t.context.logger}, '', pluginConf, {}); const plugin = normalize({cwd, options, logger: t.context.logger}, '', pluginConf, {});
await plugin({param: 'param'}); await plugin({options: {}, param: 'param'});
t.true( t.true(
pluginFunction.calledWithMatch( pluginFunction.calledWithMatch(
@ -172,7 +172,7 @@ test('Plugin is called with "pluginConfig" (with array definition) and input', a
const pluginConf = [pluginFunction, {conf: 'confValue'}]; const pluginConf = [pluginFunction, {conf: 'confValue'}];
const options = {global: 'globalValue'}; const options = {global: 'globalValue'};
const plugin = normalize({cwd, options, logger: t.context.logger}, '', pluginConf, {}); const plugin = normalize({cwd, options, logger: t.context.logger}, '', pluginConf, {});
await plugin({param: 'param'}); await plugin({options: {}, param: 'param'});
t.true( t.true(
pluginFunction.calledWithMatch( pluginFunction.calledWithMatch(
@ -189,7 +189,7 @@ test('Prevent plugins to modify "pluginConfig"', async t => {
const pluginConf = {path: pluginFunction, conf: {subConf: 'originalConf'}}; const pluginConf = {path: pluginFunction, conf: {subConf: 'originalConf'}};
const options = {globalConf: {globalSubConf: 'originalGlobalConf'}}; const options = {globalConf: {globalSubConf: 'originalGlobalConf'}};
const plugin = normalize({cwd, options, logger: t.context.logger}, '', pluginConf, {}); const plugin = normalize({cwd, options, logger: t.context.logger}, '', pluginConf, {});
await plugin(); await plugin({options: {}});
t.is(pluginConf.conf.subConf, 'originalConf'); t.is(pluginConf.conf.subConf, 'originalConf');
t.is(options.globalConf.globalSubConf, 'originalGlobalConf'); t.is(options.globalConf.globalSubConf, 'originalGlobalConf');
@ -199,7 +199,7 @@ test('Prevent plugins to modify its input', async t => {
const pluginFunction = stub().callsFake((pluginConfig, options) => { const pluginFunction = stub().callsFake((pluginConfig, options) => {
options.param.subParam = 'otherParam'; options.param.subParam = 'otherParam';
}); });
const input = {param: {subParam: 'originalSubParam'}}; const input = {param: {subParam: 'originalSubParam'}, options: {}};
const plugin = normalize({cwd, options: {}, logger: t.context.logger}, '', pluginFunction, {}); const plugin = normalize({cwd, options: {}, logger: t.context.logger}, '', pluginFunction, {});
await plugin(input); await plugin(input);
@ -220,7 +220,7 @@ test('Always pass a defined "pluginConfig" for plugin defined with string', asyn
'./test/fixtures/plugin-result-config', './test/fixtures/plugin-result-config',
{} {}
); );
const pluginResult = await plugin(); const pluginResult = await plugin({options: {}});
t.deepEqual(pluginResult.pluginConfig, {}); t.deepEqual(pluginResult.pluginConfig, {});
}); });
@ -233,7 +233,7 @@ test('Always pass a defined "pluginConfig" for plugin defined with path', async
{path: './test/fixtures/plugin-result-config'}, {path: './test/fixtures/plugin-result-config'},
{} {}
); );
const pluginResult = await plugin(); const pluginResult = await plugin({options: {}});
t.deepEqual(pluginResult.pluginConfig, {}); t.deepEqual(pluginResult.pluginConfig, {});
}); });

View File

@ -63,14 +63,14 @@ test('Export plugins based on "plugins" config (array)', async t => {
{} {}
); );
await plugins.verifyConditions({}); await plugins.verifyConditions({options: {}});
t.true(plugin1.verifyConditions.calledOnce); t.true(plugin1.verifyConditions.calledOnce);
t.true(plugin2.verifyConditions.calledOnce); t.true(plugin2.verifyConditions.calledOnce);
await plugins.publish({}); await plugins.publish({options: {}});
t.true(plugin1.publish.calledOnce); t.true(plugin1.publish.calledOnce);
await plugins.verifyRelease({}); await plugins.verifyRelease({options: {}});
t.true(plugin2.verifyRelease.notCalled); t.true(plugin2.verifyRelease.notCalled);
// Verify the module returns a function for each plugin // Verify the module returns a function for each plugin
@ -88,10 +88,10 @@ test('Export plugins based on "plugins" config (single definition)', async t =>
const plugin1 = {verifyConditions: stub(), publish: stub()}; const plugin1 = {verifyConditions: stub(), publish: stub()};
const plugins = getPlugins({cwd, logger: t.context.logger, options: {plugins: plugin1}}, {}); const plugins = getPlugins({cwd, logger: t.context.logger, options: {plugins: plugin1}}, {});
await plugins.verifyConditions({}); await plugins.verifyConditions({options: {}});
t.true(plugin1.verifyConditions.calledOnce); t.true(plugin1.verifyConditions.calledOnce);
await plugins.publish({}); await plugins.publish({options: {}});
t.true(plugin1.publish.calledOnce); t.true(plugin1.publish.calledOnce);
// Verify the module returns a function for each plugin // Verify the module returns a function for each plugin
@ -118,14 +118,14 @@ test('Merge global options, "plugins" options and step options', async t => {
{} {}
); );
await plugins.verifyConditions({}); await plugins.verifyConditions({options: {}});
t.deepEqual(plugin1[0].verifyConditions.args[0][0], {globalOpt: 'global', pluginOpt1: 'plugin1'}); t.deepEqual(plugin1[0].verifyConditions.args[0][0], {globalOpt: 'global', pluginOpt1: 'plugin1'});
t.deepEqual(plugin2[0].verifyConditions.args[0][0], {globalOpt: 'global', pluginOpt2: 'plugin2'}); t.deepEqual(plugin2[0].verifyConditions.args[0][0], {globalOpt: 'global', pluginOpt2: 'plugin2'});
await plugins.publish({}); await plugins.publish({options: {}});
t.deepEqual(plugin1[0].publish.args[0][0], {globalOpt: 'global', pluginOpt1: 'plugin1'}); t.deepEqual(plugin1[0].publish.args[0][0], {globalOpt: 'global', pluginOpt1: 'plugin1'});
await plugins.verifyRelease({}); await plugins.verifyRelease({options: {}});
t.deepEqual(plugin3[0].args[0][0], {globalOpt: 'global', pluginOpt3: 'plugin3'}); t.deepEqual(plugin3[0].args[0][0], {globalOpt: 'global', pluginOpt3: 'plugin3'});
}); });
@ -248,7 +248,7 @@ test('Merge global options with plugin options', async t => {
{} {}
); );
const [result] = await plugins.verifyRelease(); const [result] = await plugins.verifyRelease({options: {}});
t.deepEqual(result.pluginConfig, {localOpt: 'local', globalOpt: 'global', otherOpt: 'locally-defined'}); t.deepEqual(result.pluginConfig, {localOpt: 'local', globalOpt: 'global', otherOpt: 'locally-defined'});
}); });