feat: log all verification errors
This commit is contained in:
		
							parent
							
								
									03e117be10
								
							
						
					
					
						commit
						cdb98f919f
					
				
							
								
								
									
										6
									
								
								cli.js
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								cli.js
									
									
									
									
									
								
							| @ -1,6 +1,5 @@ | |||||||
| const program = require('commander'); | const program = require('commander'); | ||||||
| const {pickBy, isUndefined} = require('lodash'); | const {pickBy, isUndefined} = require('lodash'); | ||||||
| const logger = require('./lib/logger'); |  | ||||||
| 
 | 
 | ||||||
| function list(values) { | function list(values) { | ||||||
|   return values.split(',').map(value => value.trim()); |   return values.split(',').map(value => value.trim()); | ||||||
| @ -56,10 +55,5 @@ module.exports = async () => { | |||||||
|     } |     } | ||||||
|   } catch (err) { |   } catch (err) { | ||||||
|     process.exitCode = 1; |     process.exitCode = 1; | ||||||
|     if (err.semanticRelease) { |  | ||||||
|       logger.log(`%s ${err.message}`, err.code); |  | ||||||
|     } else { |  | ||||||
|       logger.error('An error occurred while running semantic-release: %O', err); |  | ||||||
|     } |  | ||||||
|   } |   } | ||||||
| }; | }; | ||||||
|  | |||||||
							
								
								
									
										25
									
								
								index.js
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								index.js
									
									
									
									
									
								
							| @ -7,7 +7,7 @@ const getCommits = require('./lib/get-commits'); | |||||||
| const logger = require('./lib/logger'); | const logger = require('./lib/logger'); | ||||||
| const {gitHead: getGitHead, isGitRepo} = require('./lib/git'); | const {gitHead: getGitHead, isGitRepo} = require('./lib/git'); | ||||||
| 
 | 
 | ||||||
| module.exports = async opts => { | async function run(opts) { | ||||||
|   const {isCi, branch, isPr} = envCi(); |   const {isCi, branch, isPr} = envCi(); | ||||||
|   const config = await getConfig(opts, logger); |   const config = await getConfig(opts, logger); | ||||||
|   const {plugins, options} = config; |   const {plugins, options} = config; | ||||||
| @ -39,7 +39,7 @@ module.exports = async opts => { | |||||||
|   logger.log('Run automated release from branch %s', options.branch); |   logger.log('Run automated release from branch %s', options.branch); | ||||||
| 
 | 
 | ||||||
|   logger.log('Call plugin %s', 'verify-conditions'); |   logger.log('Call plugin %s', 'verify-conditions'); | ||||||
|   await plugins.verifyConditions({options, logger}); |   await plugins.verifyConditions({options, logger}, true); | ||||||
| 
 | 
 | ||||||
|   logger.log('Call plugin %s', 'get-last-release'); |   logger.log('Call plugin %s', 'get-last-release'); | ||||||
|   const {commits, lastRelease} = await getCommits( |   const {commits, lastRelease} = await getCommits( | ||||||
| @ -63,7 +63,7 @@ module.exports = async opts => { | |||||||
|   const nextRelease = {type, version, gitHead: await getGitHead(), gitTag: `v${version}`}; |   const nextRelease = {type, version, gitHead: await getGitHead(), gitTag: `v${version}`}; | ||||||
| 
 | 
 | ||||||
|   logger.log('Call plugin %s', 'verify-release'); |   logger.log('Call plugin %s', 'verify-release'); | ||||||
|   await plugins.verifyRelease({options, logger, lastRelease, commits, nextRelease}); |   await plugins.verifyRelease({options, logger, lastRelease, commits, nextRelease}, true); | ||||||
| 
 | 
 | ||||||
|   const generateNotesParam = {options, logger, lastRelease, commits, nextRelease}; |   const generateNotesParam = {options, logger, lastRelease, commits, nextRelease}; | ||||||
| 
 | 
 | ||||||
| @ -78,7 +78,7 @@ module.exports = async opts => { | |||||||
|     nextRelease.notes = await plugins.generateNotes(generateNotesParam); |     nextRelease.notes = await plugins.generateNotes(generateNotesParam); | ||||||
| 
 | 
 | ||||||
|     logger.log('Call plugin %s', 'publish'); |     logger.log('Call plugin %s', 'publish'); | ||||||
|     await plugins.publish({options, logger, lastRelease, commits, nextRelease}, async prevInput => { |     await plugins.publish({options, logger, lastRelease, commits, nextRelease}, false, async prevInput => { | ||||||
|       const newGitHead = await getGitHead(); |       const newGitHead = await getGitHead(); | ||||||
|       // If previous publish plugin has created a commit (gitHead changed)
 |       // If previous publish plugin has created a commit (gitHead changed)
 | ||||||
|       if (prevInput.nextRelease.gitHead !== newGitHead) { |       if (prevInput.nextRelease.gitHead !== newGitHead) { | ||||||
| @ -93,4 +93,21 @@ module.exports = async opts => { | |||||||
|     logger.log('Published release: %s', nextRelease.version); |     logger.log('Published release: %s', nextRelease.version); | ||||||
|   } |   } | ||||||
|   return true; |   return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | module.exports = async opts => { | ||||||
|  |   try { | ||||||
|  |     const result = await run(opts); | ||||||
|  |     return result; | ||||||
|  |   } catch (err) { | ||||||
|  |     const errors = err.name === 'AggregateError' ? Array.from(err).sort(error => !error.semanticRelease) : [err]; | ||||||
|  |     for (const error of errors) { | ||||||
|  |       if (error.semanticRelease) { | ||||||
|  |         logger.log(`%s ${error.message}`, error.code); | ||||||
|  |       } else { | ||||||
|  |         logger.error('An error occurred while running semantic-release: %O', error); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     throw err; | ||||||
|  |   } | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -1,19 +1,33 @@ | |||||||
| const {identity} = require('lodash'); | const {identity} = require('lodash'); | ||||||
|  | const pReflect = require('p-reflect'); | ||||||
| const pReduce = require('p-reduce'); | const pReduce = require('p-reduce'); | ||||||
|  | const AggregateError = require('aggregate-error'); | ||||||
| 
 | 
 | ||||||
| module.exports = steps => async (input, getNextInput = identity) => { | module.exports = steps => async (input, settleAll = false, getNextInput = identity) => { | ||||||
|   const results = []; |   const results = []; | ||||||
|  |   const errors = []; | ||||||
|   await pReduce( |   await pReduce( | ||||||
|     steps, |     steps, | ||||||
|     async (prevResult, nextStep) => { |     async (prevResult, nextStep) => { | ||||||
|       // Call the next step with the input computed at the end of the previous iteration
 |       let result; | ||||||
|       const result = await nextStep(prevResult); | 
 | ||||||
|       // Save intermediary result
 |       // Call the next step with the input computed at the end of the previous iteration and save intermediary result
 | ||||||
|  |       if (settleAll) { | ||||||
|  |         const {isFulfilled, value, reason} = await pReflect(nextStep(prevResult)); | ||||||
|  |         result = isFulfilled ? value : reason; | ||||||
|  |         (isFulfilled ? results : errors).push(result); | ||||||
|  |       } else { | ||||||
|  |         result = await nextStep(prevResult); | ||||||
|         results.push(result); |         results.push(result); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|       // Prepare input for next step, passing the result of the previous iteration and the current one
 |       // Prepare input for next step, passing the result of the previous iteration and the current one
 | ||||||
|       return getNextInput(prevResult, result); |       return getNextInput(prevResult, result); | ||||||
|     }, |     }, | ||||||
|     input |     input | ||||||
|   ); |   ); | ||||||
|  |   if (errors.length > 0) { | ||||||
|  |     throw new AggregateError(errors); | ||||||
|  |   } | ||||||
|   return results; |   return results; | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -24,6 +24,7 @@ | |||||||
|     "@semantic-release/github": "^3.0.1", |     "@semantic-release/github": "^3.0.1", | ||||||
|     "@semantic-release/npm": "^2.0.0", |     "@semantic-release/npm": "^2.0.0", | ||||||
|     "@semantic-release/release-notes-generator": "^6.0.0", |     "@semantic-release/release-notes-generator": "^6.0.0", | ||||||
|  |     "aggregate-error": "^1.0.0", | ||||||
|     "chalk": "^2.3.0", |     "chalk": "^2.3.0", | ||||||
|     "commander": "^2.11.0", |     "commander": "^2.11.0", | ||||||
|     "cosmiconfig": "^4.0.0", |     "cosmiconfig": "^4.0.0", | ||||||
| @ -36,6 +37,7 @@ | |||||||
|     "marked": "^0.3.9", |     "marked": "^0.3.9", | ||||||
|     "marked-terminal": "^2.0.0", |     "marked-terminal": "^2.0.0", | ||||||
|     "p-reduce": "^1.0.0", |     "p-reduce": "^1.0.0", | ||||||
|  |     "p-reflect": "^1.0.0", | ||||||
|     "read-pkg-up": "^3.0.0", |     "read-pkg-up": "^3.0.0", | ||||||
|     "resolve-from": "^4.0.0", |     "resolve-from": "^4.0.0", | ||||||
|     "semver": "^5.4.1" |     "semver": "^5.4.1" | ||||||
|  | |||||||
| @ -2,6 +2,7 @@ import test from 'ava'; | |||||||
| import proxyquire from 'proxyquire'; | import proxyquire from 'proxyquire'; | ||||||
| import {stub} from 'sinon'; | import {stub} from 'sinon'; | ||||||
| import tempy from 'tempy'; | import tempy from 'tempy'; | ||||||
|  | import SemanticReleaseError from '@semantic-release/error'; | ||||||
| import DEFINITIONS from '../lib/plugins/definitions'; | import DEFINITIONS from '../lib/plugins/definitions'; | ||||||
| import {gitHead as getGitHead} from '../lib/git'; | import {gitHead as getGitHead} from '../lib/git'; | ||||||
| import {gitRepo, gitCommits, gitTagVersion} from './helpers/git-utils'; | import {gitRepo, gitCommits, gitTagVersion} from './helpers/git-utils'; | ||||||
| @ -161,6 +162,70 @@ test.serial('Use new gitHead, and recreate release notes if a publish plugin cre | |||||||
|   t.deepEqual(publish2.args[0][1].nextRelease, Object.assign({}, nextRelease, {notes})); |   t.deepEqual(publish2.args[0][1].nextRelease, Object.assign({}, nextRelease, {notes})); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
|  | test.serial('Log all "verifyConditions" errors', async t => { | ||||||
|  |   // Create a git repository, set the current working directory at the root of the repo
 | ||||||
|  |   await gitRepo(); | ||||||
|  |   // Add commits to the master branch
 | ||||||
|  |   await gitCommits(['First']); | ||||||
|  | 
 | ||||||
|  |   const error1 = new Error('error 1'); | ||||||
|  |   const error2 = new SemanticReleaseError('error 2', 'ERR2'); | ||||||
|  |   const error3 = new SemanticReleaseError('error 3', 'ERR3'); | ||||||
|  |   const options = { | ||||||
|  |     branch: 'master', | ||||||
|  |     repositoryUrl: 'git@hostname.com:owner/module.git', | ||||||
|  |     verifyConditions: [stub().rejects(error1), stub().rejects(error2), stub().rejects(error3)], | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   const semanticRelease = proxyquire('..', { | ||||||
|  |     './lib/logger': t.context.logger, | ||||||
|  |     'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), | ||||||
|  |   }); | ||||||
|  |   const errors = await t.throws(semanticRelease(options)); | ||||||
|  | 
 | ||||||
|  |   t.deepEqual(Array.from(errors), [error1, error2, error3]); | ||||||
|  |   t.deepEqual(t.context.log.args[t.context.log.args.length - 2], ['%s error 2', 'ERR2']); | ||||||
|  |   t.deepEqual(t.context.log.args[t.context.log.args.length - 1], ['%s error 3', 'ERR3']); | ||||||
|  |   t.deepEqual(t.context.error.args[t.context.error.args.length - 1], [ | ||||||
|  |     'An error occurred while running semantic-release: %O', | ||||||
|  |     error1, | ||||||
|  |   ]); | ||||||
|  |   t.true(t.context.error.calledAfter(t.context.log)); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test.serial('Log all "verifyRelease" errors', async t => { | ||||||
|  |   // Create a git repository, set the current working directory at the root of the repo
 | ||||||
|  |   await gitRepo(); | ||||||
|  |   // Add commits to the master branch
 | ||||||
|  |   let commits = await gitCommits(['First']); | ||||||
|  |   // Create the tag corresponding to version 1.0.0
 | ||||||
|  |   await gitTagVersion('v1.0.0'); | ||||||
|  |   // Add new commits to the master branch
 | ||||||
|  |   commits = (await gitCommits(['Second'])).concat(commits); | ||||||
|  | 
 | ||||||
|  |   const error1 = new SemanticReleaseError('error 1', 'ERR1'); | ||||||
|  |   const error2 = new SemanticReleaseError('error 2', 'ERR2'); | ||||||
|  |   const lastRelease = {version: '1.0.0', gitHead: commits[commits.length - 1].hash, gitTag: 'v1.0.0'}; | ||||||
|  |   const options = { | ||||||
|  |     branch: 'master', | ||||||
|  |     repositoryUrl: 'git@hostname.com:owner/module.git', | ||||||
|  |     verifyConditions: stub().resolves(), | ||||||
|  |     getLastRelease: stub().resolves(lastRelease), | ||||||
|  |     analyzeCommits: stub().resolves('major'), | ||||||
|  |     verifyRelease: [stub().rejects(error1), stub().rejects(error2)], | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   const semanticRelease = proxyquire('..', { | ||||||
|  |     './lib/logger': t.context.logger, | ||||||
|  |     'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), | ||||||
|  |   }); | ||||||
|  |   const errors = await t.throws(semanticRelease(options)); | ||||||
|  | 
 | ||||||
|  |   t.deepEqual(Array.from(errors), [error1, error2]); | ||||||
|  |   t.deepEqual(t.context.log.args[t.context.log.args.length - 2], ['%s error 1', 'ERR1']); | ||||||
|  |   t.deepEqual(t.context.log.args[t.context.log.args.length - 1], ['%s error 2', 'ERR2']); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
| test.serial('Dry-run skips publish', async t => { | test.serial('Dry-run skips publish', async t => { | ||||||
|   // Create a git repository, set the current working directory at the root of the repo
 |   // Create a git repository, set the current working directory at the root of the repo
 | ||||||
|   await gitRepo(); |   await gitRepo(); | ||||||
|  | |||||||
| @ -12,24 +12,50 @@ test('Execute each function in series passing the same input', async t => { | |||||||
|   t.true(step1.calledWith(0)); |   t.true(step1.calledWith(0)); | ||||||
|   t.true(step2.calledWith(0)); |   t.true(step2.calledWith(0)); | ||||||
|   t.true(step3.calledWith(0)); |   t.true(step3.calledWith(0)); | ||||||
|  | 
 | ||||||
|  |   t.true(step1.calledBefore(step2)); | ||||||
|  |   t.true(step2.calledBefore(step3)); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| test('Execute each function in series passing a transformed input', async t => { | test('Execute each function in series passing a transformed input', async t => { | ||||||
|   const step1 = stub().resolves(1); |   const step1 = stub().resolves(1); | ||||||
|   const step2 = stub().resolves(2); |   const step2 = stub().resolves(2); | ||||||
|   const step3 = stub().resolves(3); |   const step3 = stub().resolves(3); | ||||||
|  |   const step4 = stub().resolves(4); | ||||||
| 
 | 
 | ||||||
|   const result = await pipeline([step1, step2, step3])(0, (prevResult, result) => prevResult + result); |   const result = await pipeline([step1, step2, step3, step4])(0, false, (prevResult, result) => prevResult + result); | ||||||
| 
 | 
 | ||||||
|   t.deepEqual(result, [1, 2, 3]); |   t.deepEqual(result, [1, 2, 3, 4]); | ||||||
|  |   t.true(step1.calledWith(0)); | ||||||
|  |   t.true(step2.calledWith(0 + 1)); | ||||||
|  |   t.true(step3.calledWith(0 + 1 + 2)); | ||||||
|  |   t.true(step4.calledWith(0 + 1 + 2 + 3)); | ||||||
|  |   t.true(step1.calledBefore(step2)); | ||||||
|  |   t.true(step2.calledBefore(step3)); | ||||||
|  |   t.true(step3.calledBefore(step4)); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test('Execute each function in series passing the result of the previous one', async t => { | ||||||
|  |   const step1 = stub().resolves(1); | ||||||
|  |   const step2 = stub().resolves(2); | ||||||
|  |   const step3 = stub().resolves(3); | ||||||
|  |   const step4 = stub().resolves(4); | ||||||
|  | 
 | ||||||
|  |   const result = await pipeline([step1, step2, step3, step4])(0, false, (prevResult, result) => result); | ||||||
|  | 
 | ||||||
|  |   t.deepEqual(result, [1, 2, 3, 4]); | ||||||
|   t.true(step1.calledWith(0)); |   t.true(step1.calledWith(0)); | ||||||
|   t.true(step2.calledWith(1)); |   t.true(step2.calledWith(1)); | ||||||
|   t.true(step3.calledWith(3)); |   t.true(step3.calledWith(2)); | ||||||
|  |   t.true(step4.calledWith(3)); | ||||||
|  |   t.true(step1.calledBefore(step2)); | ||||||
|  |   t.true(step2.calledBefore(step3)); | ||||||
|  |   t.true(step3.calledBefore(step4)); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| test('Stop execution and throw error is a step rejects', async t => { | test('Stop execution and throw error is a step rejects', async t => { | ||||||
|   const step1 = stub().resolves(1); |   const step1 = stub().resolves(1); | ||||||
|   const step2 = stub().throws(new Error('test error')); |   const step2 = stub().rejects(new Error('test error')); | ||||||
|   const step3 = stub().resolves(3); |   const step3 = stub().resolves(3); | ||||||
| 
 | 
 | ||||||
|   const error = await t.throws(pipeline([step1, step2, step3])(0), Error); |   const error = await t.throws(pipeline([step1, step2, step3])(0), Error); | ||||||
| @ -38,3 +64,37 @@ test('Stop execution and throw error is a step rejects', async t => { | |||||||
|   t.true(step2.calledWith(0)); |   t.true(step2.calledWith(0)); | ||||||
|   t.true(step3.notCalled); |   t.true(step3.notCalled); | ||||||
| }); | }); | ||||||
|  | 
 | ||||||
|  | test('Execute all even if a Promise rejects', async t => { | ||||||
|  |   const error1 = new Error('test error 1'); | ||||||
|  |   const error2 = new Error('test error 2'); | ||||||
|  |   const step1 = stub().resolves(1); | ||||||
|  |   const step2 = stub().rejects(error1); | ||||||
|  |   const step3 = stub().rejects(error2); | ||||||
|  | 
 | ||||||
|  |   const errors = await t.throws(pipeline([step1, step2, step3])(0, true)); | ||||||
|  | 
 | ||||||
|  |   t.deepEqual(Array.from(errors), [error1, error2]); | ||||||
|  |   t.true(step1.calledWith(0)); | ||||||
|  |   t.true(step2.calledWith(0)); | ||||||
|  |   t.true(step3.calledWith(0)); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test('Execute each function in series passing a transformed input even if a Promise rejects', async t => { | ||||||
|  |   const error2 = new Error('test error 2'); | ||||||
|  |   const error3 = new Error('test error 3'); | ||||||
|  |   const step1 = stub().resolves(1); | ||||||
|  |   const step2 = stub().rejects(error2); | ||||||
|  |   const step3 = stub().rejects(error3); | ||||||
|  |   const step4 = stub().resolves(4); | ||||||
|  | 
 | ||||||
|  |   const errors = await t.throws( | ||||||
|  |     pipeline([step1, step2, step3, step4])(0, true, (prevResult, result) => prevResult + result) | ||||||
|  |   ); | ||||||
|  | 
 | ||||||
|  |   t.deepEqual(Array.from(errors), [error2, error3]); | ||||||
|  |   t.true(step1.calledWith(0)); | ||||||
|  |   t.true(step2.calledWith(0 + 1)); | ||||||
|  |   t.true(step3.calledWith(0 + 1 + error2)); | ||||||
|  |   t.true(step4.calledWith(0 + 1 + error2 + error3)); | ||||||
|  | }); | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user