diff --git a/lib/get-config.js b/lib/get-config.js index 36ec9494..422dd9b5 100644 --- a/lib/get-config.js +++ b/lib/get-config.js @@ -1,8 +1,7 @@ -const readPkgUp = require('read-pkg-up'); const {castArray, pickBy, isUndefined, isNull, isString, isPlainObject} = require('lodash'); +const readPkgUp = require('read-pkg-up'); const cosmiconfig = require('cosmiconfig'); const resolveFrom = require('resolve-from'); -const SemanticReleaseError = require('@semantic-release/error'); const debug = require('debug')('semantic-release:config'); const {repoUrl} = require('./git'); const PLUGINS_DEFINITION = require('./plugins/definitions'); @@ -61,11 +60,7 @@ module.exports = async (opts, logger) => { debug('verifyRelease: %O', options.verifyRelease); debug('publish: %O', options.publish); - if (!options.repositoryUrl) { - throw new SemanticReleaseError('The repositoryUrl option is required', 'ENOREPOURL'); - } - - options.repositoryUrl = getGitAuthUrl(options.repositoryUrl); + options.repositoryUrl = options.repositoryUrl ? getGitAuthUrl(options.repositoryUrl) : options.repositoryUrl; return {options, plugins: await plugins(options, pluginsPath, logger)}; }; diff --git a/lib/plugins/index.js b/lib/plugins/index.js index 9bbd16d9..c5808c0d 100644 --- a/lib/plugins/index.js +++ b/lib/plugins/index.js @@ -1,11 +1,13 @@ const {isArray, isObject, omit} = require('lodash'); +const AggregateError = require('aggregate-error'); const SemanticReleaseError = require('@semantic-release/error'); const PLUGINS_DEFINITION = require('./definitions'); const pipeline = require('./pipeline'); const normalize = require('./normalize'); -module.exports = (options, pluginsPath, logger) => - Object.keys(PLUGINS_DEFINITION).reduce((plugins, pluginType) => { +module.exports = (options, pluginsPath, logger) => { + const errors = []; + const plugins = Object.keys(PLUGINS_DEFINITION).reduce((plugins, pluginType) => { const {config, output, default: def} = PLUGINS_DEFINITION[pluginType]; let pluginConfs; if (options[pluginType]) { @@ -14,7 +16,8 @@ module.exports = (options, pluginsPath, logger) => options[pluginType].path = def; } if (config && !config.validator(options[pluginType])) { - throw new SemanticReleaseError(config.message, 'EPLUGINCONF'); + errors.push(new SemanticReleaseError(config.message, 'EPLUGINCONF')); + return plugins; } pluginConfs = options[pluginType]; } else { @@ -29,3 +32,8 @@ module.exports = (options, pluginsPath, logger) => return plugins; }, {}); + if (errors.length > 0) { + throw new AggregateError(errors); + } + return plugins; +}; diff --git a/lib/verify.js b/lib/verify.js index c68c5f09..f4050865 100644 --- a/lib/verify.js +++ b/lib/verify.js @@ -1,19 +1,30 @@ const SemanticReleaseError = require('@semantic-release/error'); +const AggregateError = require('aggregate-error'); const {isGitRepo, verifyAuth} = require('./git'); module.exports = async (options, branch, logger) => { + const errors = []; + if (!await isGitRepo()) { logger.error('Semantic-release must run from a git repository.'); return false; } - if (!await verifyAuth(options.repositoryUrl, options.branch)) { - throw new SemanticReleaseError( - `The git credentials doesn't allow to push on the branch ${options.branch}.`, - 'EGITNOPERMISSION' + if (!options.repositoryUrl) { + errors.push(new SemanticReleaseError('The repositoryUrl option is required', 'ENOREPOURL')); + } else if (!await verifyAuth(options.repositoryUrl, options.branch)) { + errors.push( + new SemanticReleaseError( + `The git credentials doesn't allow to push on the branch ${options.branch}.`, + 'EGITNOPERMISSION' + ) ); } + if (errors.length > 0) { + throw new AggregateError(errors); + } + if (branch !== options.branch) { logger.log( `This test run was triggered on the branch ${branch}, while semantic-release is configured to only publish from ${ diff --git a/test/get-config.test.js b/test/get-config.test.js index 4c492f52..4cb362aa 100644 --- a/test/get-config.test.js +++ b/test/get-config.test.js @@ -13,12 +13,12 @@ const envBackup = Object.assign({}, process.env); const cwd = process.cwd(); test.beforeEach(t => { - // Delete environment variables that could have been set on the machine running the tests delete process.env.GIT_CREDENTIALS; delete process.env.GH_TOKEN; delete process.env.GITHUB_TOKEN; delete process.env.GL_TOKEN; delete process.env.GITLAB_TOKEN; + // Delete environment variables that could have been set on the machine running the tests t.context.plugins = stub().returns({}); t.context.getConfig = proxyquire('../lib/get-config', {'./plugins': t.context.plugins}); }); diff --git a/test/index.test.js b/test/index.test.js index 3e0e2f3e..9f378a5f 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -601,11 +601,11 @@ test.serial('Throw SemanticReleaseError if repositoryUrl is not set and cannot b './lib/logger': t.context.logger, 'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), }); - const error = await t.throws(semanticRelease()); + const errors = Array.from(await t.throws(semanticRelease())); // Verify error code and type - t.is(error.code, 'ENOREPOURL'); - t.is(error.name, 'SemanticReleaseError'); + t.is(errors[0].code, 'ENOREPOURL'); + t.is(errors[0].name, 'SemanticReleaseError'); }); test.serial('Throw an Error if plugin returns an unexpected value', async t => { diff --git a/test/plugins/plugins.test.js b/test/plugins/plugins.test.js index 1c0a5e7e..f60727c4 100644 --- a/test/plugins/plugins.test.js +++ b/test/plugins/plugins.test.js @@ -127,26 +127,34 @@ test('Merge global options with plugin options', async t => { 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 => { - const error = t.throws(() => getPlugins({verifyConditions: {}}, {}, t.context.logger)); +test('Throw an error if plugins configuration are missing a path for plugin pipeline', t => { + const errors = Array.from( + t.throws(() => getPlugins({verifyConditions: {}, verifyRelease: {}}, {}, t.context.logger)) + ); - t.is(error.name, 'SemanticReleaseError'); - t.is(error.code, 'EPLUGINCONF'); + t.is(errors[0].name, 'SemanticReleaseError'); + t.is(errors[0].code, 'EPLUGINCONF'); t.is( - error.message, + errors[0].message, 'The "verifyConditions" plugin, if defined, must be a single or an array of plugins definition. A plugin definition is either a string or an object with a path property.' ); + t.is(errors[1].name, 'SemanticReleaseError'); + t.is(errors[1].code, 'EPLUGINCONF'); + t.is( + errors[1].message, + 'The "verifyRelease" plugin, if defined, must be a single or an array of plugins definition. A plugin definition is either a string or an object with a path property.' + ); }); test('Throw an error if an array of plugin configuration is missing a path for plugin pipeline', t => { - const error = t.throws(() => - getPlugins({verifyConditions: [{path: '@semantic-release/npm'}, {}]}, {}, t.context.logger) + const errors = Array.from( + t.throws(() => getPlugins({verifyConditions: [{path: '@semantic-release/npm'}, {}]}, {}, t.context.logger)) ); - t.is(error.name, 'SemanticReleaseError'); - t.is(error.code, 'EPLUGINCONF'); + t.is(errors[0].name, 'SemanticReleaseError'); + t.is(errors[0].code, 'EPLUGINCONF'); t.is( - error.message, + errors[0].message, 'The "verifyConditions" plugin, if defined, must be a single or an array of plugins definition. A plugin definition is either a string or an object with a path property.' ); });