refactor: build plugin pipeline parameters at initialization
In addition, factorize the pipeline config function to avoid code duplication.
This commit is contained in:
		
							parent
							
								
									eb26254b00
								
							
						
					
					
						commit
						24ce560065
					
				
							
								
								
									
										88
									
								
								index.js
									
									
									
									
									
								
							
							
						
						
									
										88
									
								
								index.js
									
									
									
									
									
								
							| @ -1,4 +1,4 @@ | |||||||
| const {template, isPlainObject} = require('lodash'); | const {template} = require('lodash'); | ||||||
| const marked = require('marked'); | const marked = require('marked'); | ||||||
| const TerminalRenderer = require('marked-terminal'); | const TerminalRenderer = require('marked-terminal'); | ||||||
| const envCi = require('env-ci'); | const envCi = require('env-ci'); | ||||||
| @ -15,7 +15,7 @@ const getGitAuthUrl = require('./lib/get-git-auth-url'); | |||||||
| const logger = require('./lib/logger'); | const logger = require('./lib/logger'); | ||||||
| const {fetch, verifyAuth, isBranchUpToDate, gitHead: getGitHead, tag, push} = require('./lib/git'); | const {fetch, verifyAuth, isBranchUpToDate, gitHead: getGitHead, tag, push} = require('./lib/git'); | ||||||
| const getError = require('./lib/get-error'); | const getError = require('./lib/get-error'); | ||||||
| const {COMMIT_NAME, COMMIT_EMAIL, RELEASE_NOTES_SEPARATOR} = require('./lib/definitions/constants'); | const {COMMIT_NAME, COMMIT_EMAIL} = require('./lib/definitions/constants'); | ||||||
| 
 | 
 | ||||||
| marked.setOptions({renderer: new TerminalRenderer()}); | marked.setOptions({renderer: new TerminalRenderer()}); | ||||||
| 
 | 
 | ||||||
| @ -72,21 +72,14 @@ async function run(options, plugins) { | |||||||
| 
 | 
 | ||||||
|   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'); |   await plugins.verifyConditions({options, logger}); | ||||||
|   await plugins.verifyConditions({options, logger}, {settleAll: true}); |  | ||||||
| 
 | 
 | ||||||
|   await fetch(options.repositoryUrl); |   await fetch(options.repositoryUrl); | ||||||
| 
 | 
 | ||||||
|   const lastRelease = await getLastRelease(options.tagFormat, logger); |   const lastRelease = await getLastRelease(options.tagFormat, logger); | ||||||
|   const commits = await getCommits(lastRelease.gitHead, options.branch, logger); |   const commits = await getCommits(lastRelease.gitHead, options.branch, logger); | ||||||
| 
 | 
 | ||||||
|   logger.log('Call plugin %s', 'analyze-commits'); |   const type = await plugins.analyzeCommits({options, logger, lastRelease, commits}); | ||||||
|   const [type] = await plugins.analyzeCommits({ |  | ||||||
|     options, |  | ||||||
|     logger, |  | ||||||
|     lastRelease, |  | ||||||
|     commits: commits.filter(commit => !/\[skip\s+release\]|\[release\s+skip\]/i.test(commit.message)), |  | ||||||
|   }); |  | ||||||
|   if (!type) { |   if (!type) { | ||||||
|     logger.log('There are no relevant changes, so no new version is released.'); |     logger.log('There are no relevant changes, so no new version is released.'); | ||||||
|     return; |     return; | ||||||
| @ -94,87 +87,28 @@ async function run(options, plugins) { | |||||||
|   const version = getNextVersion(type, lastRelease, logger); |   const version = getNextVersion(type, lastRelease, logger); | ||||||
|   const nextRelease = {type, version, gitHead: await getGitHead(), gitTag: template(options.tagFormat)({version})}; |   const nextRelease = {type, version, gitHead: await getGitHead(), gitTag: template(options.tagFormat)({version})}; | ||||||
| 
 | 
 | ||||||
|   logger.log('Call plugin %s', 'verify-release'); |   await plugins.verifyRelease({options, logger, lastRelease, commits, nextRelease}); | ||||||
|   await plugins.verifyRelease({options, logger, lastRelease, commits, nextRelease}, {settleAll: true}); |  | ||||||
| 
 | 
 | ||||||
|   const generateNotesParam = {options, logger, lastRelease, commits, nextRelease}; |   const generateNotesParam = {options, logger, lastRelease, commits, nextRelease}; | ||||||
| 
 | 
 | ||||||
|   if (options.dryRun) { |   if (options.dryRun) { | ||||||
|     logger.log('Call plugin %s', 'generate-notes'); |     const notes = await plugins.generateNotes(generateNotesParam); | ||||||
|     const notes = (await plugins.generateNotes(generateNotesParam, { |  | ||||||
|       getNextInput: ({nextRelease, ...generateNotesParam}, notes) => ({ |  | ||||||
|         ...generateNotesParam, |  | ||||||
|         nextRelease: { |  | ||||||
|           ...nextRelease, |  | ||||||
|           notes: `${nextRelease.notes ? `${nextRelease.notes}${RELEASE_NOTES_SEPARATOR}` : ''}${notes}`, |  | ||||||
|         }, |  | ||||||
|       }), |  | ||||||
|     })) |  | ||||||
|       .filter(Boolean) |  | ||||||
|       .join(RELEASE_NOTES_SEPARATOR); |  | ||||||
|     logger.log('Release note for version %s:\n', nextRelease.version); |     logger.log('Release note for version %s:\n', nextRelease.version); | ||||||
|     if (notes) { |     if (notes) { | ||||||
|       process.stdout.write(`${marked(notes)}\n`); |       process.stdout.write(`${marked(notes)}\n`); | ||||||
|     } |     } | ||||||
|   } else { |   } else { | ||||||
|     logger.log('Call plugin %s', 'generateNotes'); |     nextRelease.notes = await plugins.generateNotes(generateNotesParam); | ||||||
|     nextRelease.notes = (await plugins.generateNotes(generateNotesParam, { |     await plugins.prepare({options, logger, lastRelease, commits, nextRelease}); | ||||||
|       getNextInput: ({nextRelease, ...generateNotesParam}, notes) => ({ |  | ||||||
|         ...generateNotesParam, |  | ||||||
|         nextRelease: { |  | ||||||
|           ...nextRelease, |  | ||||||
|           notes: `${nextRelease.notes ? `${nextRelease.notes}${RELEASE_NOTES_SEPARATOR}` : ''}${notes}`, |  | ||||||
|         }, |  | ||||||
|       }), |  | ||||||
|     })) |  | ||||||
|       .filter(Boolean) |  | ||||||
|       .join(RELEASE_NOTES_SEPARATOR); |  | ||||||
| 
 |  | ||||||
|     logger.log('Call plugin %s', 'prepare'); |  | ||||||
|     await plugins.prepare( |  | ||||||
|       {options, logger, lastRelease, commits, nextRelease}, |  | ||||||
|       { |  | ||||||
|         getNextInput: async ({nextRelease, ...prepareParam}) => { |  | ||||||
|           const newGitHead = await getGitHead(); |  | ||||||
|           // If previous prepare plugin has created a commit (gitHead changed)
 |  | ||||||
|           if (nextRelease.gitHead !== newGitHead) { |  | ||||||
|             nextRelease.gitHead = newGitHead; |  | ||||||
|             // Regenerate the release notes
 |  | ||||||
|             logger.log('Call plugin %s', 'generateNotes'); |  | ||||||
|             nextRelease.notes = (await plugins.generateNotes( |  | ||||||
|               {nextRelease, ...prepareParam}, |  | ||||||
|               { |  | ||||||
|                 getNextInput: ({nextRelease, ...generateNotesParam}, notes) => ({ |  | ||||||
|                   ...generateNotesParam, |  | ||||||
|                   nextRelease: { |  | ||||||
|                     ...nextRelease, |  | ||||||
|                     notes: `${nextRelease.notes ? `${nextRelease.notes}${RELEASE_NOTES_SEPARATOR}` : ''}${notes}`, |  | ||||||
|                   }, |  | ||||||
|                 }), |  | ||||||
|               } |  | ||||||
|             )) |  | ||||||
|               .filter(Boolean) |  | ||||||
|               .join(RELEASE_NOTES_SEPARATOR); |  | ||||||
|           } |  | ||||||
|           // Call the next publish plugin with the updated `nextRelease`
 |  | ||||||
|           return {...prepareParam, nextRelease}; |  | ||||||
|         }, |  | ||||||
|       } |  | ||||||
|     ); |  | ||||||
| 
 | 
 | ||||||
|     // 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
 | ||||||
|     logger.log('Create tag %s', nextRelease.gitTag); |     logger.log('Create tag %s', nextRelease.gitTag); | ||||||
|     await tag(nextRelease.gitTag); |     await tag(nextRelease.gitTag); | ||||||
|     await push(options.repositoryUrl, branch); |     await push(options.repositoryUrl, branch); | ||||||
| 
 | 
 | ||||||
|     logger.log('Call plugin %s', 'publish'); |     const releases = await plugins.publish({options, logger, lastRelease, commits, nextRelease}); | ||||||
|     const releases = await plugins.publish( |  | ||||||
|       {options, logger, lastRelease, commits, nextRelease}, |  | ||||||
|       // Add nextRelease and plugin properties to published release
 |  | ||||||
|       {transform: (release, step) => ({...(isPlainObject(release) ? release : {}), ...nextRelease, ...step})} |  | ||||||
|     ); |  | ||||||
| 
 | 
 | ||||||
|     await plugins.success({options, logger, lastRelease, commits, nextRelease, releases}, {settleAll: true}); |     await plugins.success({options, logger, lastRelease, commits, nextRelease, releases}); | ||||||
| 
 | 
 | ||||||
|     logger.log('Published release: %s', nextRelease.version); |     logger.log('Published release: %s', nextRelease.version); | ||||||
|   } |   } | ||||||
| @ -199,7 +133,7 @@ async function callFail(plugins, options, error) { | |||||||
|   const errors = extractErrors(error).filter(error => error.semanticRelease); |   const errors = extractErrors(error).filter(error => error.semanticRelease); | ||||||
|   if (errors.length > 0) { |   if (errors.length > 0) { | ||||||
|     try { |     try { | ||||||
|       await plugins.fail({options, logger, errors}, {settleAll: true}); |       await plugins.fail({options, logger, errors}); | ||||||
|     } catch (err) { |     } catch (err) { | ||||||
|       logErrors(err); |       logErrors(err); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -1,5 +1,6 @@ | |||||||
| const {isString, isFunction, isArray, isPlainObject} = require('lodash'); | const {isString, isFunction, isArray, isPlainObject} = require('lodash'); | ||||||
| const {RELEASE_TYPE} = require('./constants'); | const {gitHead} = require('../git'); | ||||||
|  | const {RELEASE_TYPE, RELEASE_NOTES_SEPARATOR} = require('./constants'); | ||||||
| 
 | 
 | ||||||
| const validatePluginConfig = conf => isString(conf) || isString(conf.path) || isFunction(conf); | const validatePluginConfig = conf => isString(conf) || isString(conf.path) || isFunction(conf); | ||||||
| 
 | 
 | ||||||
| @ -7,36 +8,77 @@ module.exports = { | |||||||
|   verifyConditions: { |   verifyConditions: { | ||||||
|     default: ['@semantic-release/npm', '@semantic-release/github'], |     default: ['@semantic-release/npm', '@semantic-release/github'], | ||||||
|     configValidator: conf => !conf || (isArray(conf) ? conf : [conf]).every(conf => validatePluginConfig(conf)), |     configValidator: conf => !conf || (isArray(conf) ? conf : [conf]).every(conf => validatePluginConfig(conf)), | ||||||
|  |     pipelineConfig: () => ({settleAll: true}), | ||||||
|   }, |   }, | ||||||
|   analyzeCommits: { |   analyzeCommits: { | ||||||
|     default: '@semantic-release/commit-analyzer', |     default: '@semantic-release/commit-analyzer', | ||||||
|     configValidator: conf => Boolean(conf) && validatePluginConfig(conf), |     configValidator: conf => Boolean(conf) && validatePluginConfig(conf), | ||||||
|     outputValidator: output => !output || RELEASE_TYPE.includes(output), |     outputValidator: output => !output || RELEASE_TYPE.includes(output), | ||||||
|  |     preprocess: ({commits, ...inputs}) => ({ | ||||||
|  |       ...inputs, | ||||||
|  |       commits: commits.filter(commit => !/\[skip\s+release\]|\[release\s+skip\]/i.test(commit.message)), | ||||||
|  |     }), | ||||||
|  |     postprocess: ([result]) => result, | ||||||
|   }, |   }, | ||||||
|   verifyRelease: { |   verifyRelease: { | ||||||
|     default: false, |     default: false, | ||||||
|     configValidator: conf => !conf || (isArray(conf) ? conf : [conf]).every(conf => validatePluginConfig(conf)), |     configValidator: conf => !conf || (isArray(conf) ? conf : [conf]).every(conf => validatePluginConfig(conf)), | ||||||
|  |     pipelineConfig: () => ({settleAll: true}), | ||||||
|   }, |   }, | ||||||
|   generateNotes: { |   generateNotes: { | ||||||
|     default: ['@semantic-release/release-notes-generator'], |     default: ['@semantic-release/release-notes-generator'], | ||||||
|     configValidator: conf => !conf || (isArray(conf) ? conf : [conf]).every(conf => validatePluginConfig(conf)), |     configValidator: conf => !conf || (isArray(conf) ? conf : [conf]).every(conf => validatePluginConfig(conf)), | ||||||
|     outputValidator: output => !output || isString(output), |     outputValidator: output => !output || isString(output), | ||||||
|  |     pipelineConfig: () => ({ | ||||||
|  |       getNextInput: ({nextRelease, ...generateNotesParam}, notes) => ({ | ||||||
|  |         ...generateNotesParam, | ||||||
|  |         nextRelease: { | ||||||
|  |           ...nextRelease, | ||||||
|  |           notes: `${nextRelease.notes ? `${nextRelease.notes}${RELEASE_NOTES_SEPARATOR}` : ''}${notes}`, | ||||||
|  |         }, | ||||||
|  |       }), | ||||||
|  |     }), | ||||||
|  |     postprocess: results => results.filter(Boolean).join(RELEASE_NOTES_SEPARATOR), | ||||||
|   }, |   }, | ||||||
|   prepare: { |   prepare: { | ||||||
|     default: ['@semantic-release/npm'], |     default: ['@semantic-release/npm'], | ||||||
|     configValidator: conf => !conf || (isArray(conf) ? conf : [conf]).every(conf => validatePluginConfig(conf)), |     configValidator: conf => !conf || (isArray(conf) ? conf : [conf]).every(conf => validatePluginConfig(conf)), | ||||||
|  |     pipelineConfig: ({generateNotes}, logger) => ({ | ||||||
|  |       getNextInput: async ({nextRelease, ...prepareParam}) => { | ||||||
|  |         const newGitHead = await gitHead(); | ||||||
|  |         // If previous prepare plugin has created a commit (gitHead changed)
 | ||||||
|  |         if (nextRelease.gitHead !== newGitHead) { | ||||||
|  |           nextRelease.gitHead = newGitHead; | ||||||
|  |           // Regenerate the release notes
 | ||||||
|  |           logger.log('Call plugin %s', 'generateNotes'); | ||||||
|  |           nextRelease.notes = await generateNotes({nextRelease, ...prepareParam}); | ||||||
|  |         } | ||||||
|  |         // Call the next publish plugin with the updated `nextRelease`
 | ||||||
|  |         return {...prepareParam, nextRelease}; | ||||||
|  |       }, | ||||||
|  |     }), | ||||||
|   }, |   }, | ||||||
|   publish: { |   publish: { | ||||||
|     default: ['@semantic-release/npm', '@semantic-release/github'], |     default: ['@semantic-release/npm', '@semantic-release/github'], | ||||||
|     configValidator: conf => !conf || (isArray(conf) ? conf : [conf]).every(conf => validatePluginConfig(conf)), |     configValidator: conf => !conf || (isArray(conf) ? conf : [conf]).every(conf => validatePluginConfig(conf)), | ||||||
|     outputValidator: output => !output || isPlainObject(output), |     outputValidator: output => !output || isPlainObject(output), | ||||||
|  |     pipelineConfig: () => ({ | ||||||
|  |       // Add `nextRelease` and plugin properties to published release
 | ||||||
|  |       transform: (release, step, {nextRelease}) => ({ | ||||||
|  |         ...(isPlainObject(release) ? release : {}), | ||||||
|  |         ...nextRelease, | ||||||
|  |         ...step, | ||||||
|  |       }), | ||||||
|  |     }), | ||||||
|   }, |   }, | ||||||
|   success: { |   success: { | ||||||
|     default: ['@semantic-release/github'], |     default: ['@semantic-release/github'], | ||||||
|     configValidator: conf => !conf || (isArray(conf) ? conf : [conf]).every(conf => validatePluginConfig(conf)), |     configValidator: conf => !conf || (isArray(conf) ? conf : [conf]).every(conf => validatePluginConfig(conf)), | ||||||
|  |     pipelineConfig: () => ({settleAll: true}), | ||||||
|   }, |   }, | ||||||
|   fail: { |   fail: { | ||||||
|     default: ['@semantic-release/github'], |     default: ['@semantic-release/github'], | ||||||
|     configValidator: conf => !conf || (isArray(conf) ? conf : [conf]).every(conf => validatePluginConfig(conf)), |     configValidator: conf => !conf || (isArray(conf) ? conf : [conf]).every(conf => validatePluginConfig(conf)), | ||||||
|  |     pipelineConfig: () => ({settleAll: true}), | ||||||
|   }, |   }, | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| const {isPlainObject, omit, castArray, isUndefined} = require('lodash'); | const {identity, isPlainObject, omit, castArray, isUndefined} = require('lodash'); | ||||||
| const AggregateError = require('aggregate-error'); | const AggregateError = require('aggregate-error'); | ||||||
| const getError = require('../get-error'); | const getError = require('../get-error'); | ||||||
| const PLUGINS_DEFINITIONS = require('../definitions/plugins'); | const PLUGINS_DEFINITIONS = require('../definitions/plugins'); | ||||||
| @ -7,31 +7,37 @@ const normalize = require('./normalize'); | |||||||
| 
 | 
 | ||||||
| module.exports = (options, pluginsPath, logger) => { | module.exports = (options, pluginsPath, logger) => { | ||||||
|   const errors = []; |   const errors = []; | ||||||
|   const plugins = Object.entries(PLUGINS_DEFINITIONS).reduce((plugins, [type, {configValidator, default: def}]) => { |   const plugins = Object.entries(PLUGINS_DEFINITIONS).reduce( | ||||||
|     let pluginConfs; |     ( | ||||||
|  |       plugins, | ||||||
|  |       [type, {configValidator, default: def, pipelineConfig, postprocess = identity, preprocess = identity}] | ||||||
|  |     ) => { | ||||||
|  |       let pluginConfs; | ||||||
| 
 | 
 | ||||||
|     if (isUndefined(options[type])) { |       if (isUndefined(options[type])) { | ||||||
|       pluginConfs = def; |         pluginConfs = def; | ||||||
|     } else { |       } else { | ||||||
|       // If an object is passed and the path is missing, set the default one for single plugins
 |         // If an object is passed and the path is missing, set the default one for single plugins
 | ||||||
|       if (isPlainObject(options[type]) && !options[type].path && castArray(def).length === 1) { |         if (isPlainObject(options[type]) && !options[type].path && castArray(def).length === 1) { | ||||||
|         options[type].path = def; |           options[type].path = def; | ||||||
|  |         } | ||||||
|  |         if (configValidator && !configValidator(options[type])) { | ||||||
|  |           errors.push(getError('EPLUGINCONF', {type, pluginConf: options[type]})); | ||||||
|  |           return plugins; | ||||||
|  |         } | ||||||
|  |         pluginConfs = options[type]; | ||||||
|       } |       } | ||||||
|       if (configValidator && !configValidator(options[type])) { |  | ||||||
|         errors.push(getError('EPLUGINCONF', {type, pluginConf: options[type]})); |  | ||||||
|         return plugins; |  | ||||||
|       } |  | ||||||
|       pluginConfs = options[type]; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     const globalOpts = omit(options, Object.keys(PLUGINS_DEFINITIONS)); |       const globalOpts = omit(options, Object.keys(PLUGINS_DEFINITIONS)); | ||||||
|  |       const steps = castArray(pluginConfs).map(conf => normalize(type, pluginsPath, globalOpts, conf, logger)); | ||||||
| 
 | 
 | ||||||
|     plugins[type] = pipeline( |       plugins[type] = async input => | ||||||
|       castArray(pluginConfs).map(conf => normalize(type, pluginsPath, globalOpts, conf, logger)) |         postprocess(await pipeline(steps, pipelineConfig && pipelineConfig(plugins, logger))(await preprocess(input))); | ||||||
|     ); |  | ||||||
| 
 | 
 | ||||||
|     return plugins; |       return plugins; | ||||||
|   }, {}); |     }, | ||||||
|  |     {} | ||||||
|  |   ); | ||||||
|   if (errors.length > 0) { |   if (errors.length > 0) { | ||||||
|     throw new AggregateError(errors); |     throw new AggregateError(errors); | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -42,6 +42,7 @@ module.exports = (type, pluginsPath, globalOpts, pluginOpts, logger) => { | |||||||
|   const validator = async input => { |   const validator = async input => { | ||||||
|     const {outputValidator} = PLUGINS_DEFINITIONS[type] || {}; |     const {outputValidator} = PLUGINS_DEFINITIONS[type] || {}; | ||||||
|     try { |     try { | ||||||
|  |       logger.log('Call plugin "%s"', type); | ||||||
|       const result = await func(cloneDeep(input)); |       const result = await func(cloneDeep(input)); | ||||||
|       if (outputValidator && !outputValidator(result)) { |       if (outputValidator && !outputValidator(result)) { | ||||||
|         throw getError(`E${type.toUpperCase()}OUTPUT`, {result, pluginName}); |         throw getError(`E${type.toUpperCase()}OUTPUT`, {result, pluginName}); | ||||||
|  | |||||||
| @ -8,10 +8,6 @@ const {extractErrors} = require('../utils'); | |||||||
|  * |  * | ||||||
|  * @typedef {Function} Pipeline |  * @typedef {Function} Pipeline | ||||||
|  * @param {Any} input Argument to pass to the first step in the pipeline. |  * @param {Any} input Argument to pass to the first step in the pipeline. | ||||||
|  * @param {Object} options Pipeline options. |  | ||||||
|  * @param {Boolean} [options.settleAll=false] If `true` all the steps in the pipeline are executed, even if one rejects, if `false` the execution stops after a steps rejects. |  | ||||||
|  * @param {Function} [options.getNextInput=identity] Function called after each step is executed, with the last step input and the current current step result; the returned value will be used as the input of the next step. |  | ||||||
|  * @param {Function} [options.transform=identity] Function called after each step is executed, with the current step result and the step function; the returned value will be saved in the pipeline results. |  | ||||||
|  * |  * | ||||||
|  * @return {Array<*>|*} An Array with the result of each step in the pipeline; if there is only 1 step in the pipeline, the result of this step is returned directly. |  * @return {Array<*>|*} An Array with the result of each step in the pipeline; if there is only 1 step in the pipeline, the result of this step is returned directly. | ||||||
|  * |  * | ||||||
| @ -22,9 +18,14 @@ const {extractErrors} = require('../utils'); | |||||||
|  * Create a Pipeline with a list of Functions. |  * Create a Pipeline with a list of Functions. | ||||||
|  * |  * | ||||||
|  * @param {Array<Function>} steps The list of Function to execute. |  * @param {Array<Function>} steps The list of Function to execute. | ||||||
|  |  * @param {Object} options Pipeline options. | ||||||
|  |  * @param {Boolean} [options.settleAll=false] If `true` all the steps in the pipeline are executed, even if one rejects, if `false` the execution stops after a steps rejects. | ||||||
|  |  * @param {Function} [options.getNextInput=identity] Function called after each step is executed, with the last step input and the current current step result; the returned value will be used as the input of the next step. | ||||||
|  |  * @param {Function} [options.transform=identity] Function called after each step is executed, with the current step result, the step function and the last step input; the returned value will be saved in the pipeline results. | ||||||
|  |  * | ||||||
|  * @return {Pipeline} A Function that execute the `steps` sequencially |  * @return {Pipeline} A Function that execute the `steps` sequencially | ||||||
|  */ |  */ | ||||||
| module.exports = steps => async (input, {settleAll = false, getNextInput = identity, transform = identity} = {}) => { | module.exports = (steps, {settleAll = false, getNextInput = identity, transform = identity} = {}) => async input => { | ||||||
|   const results = []; |   const results = []; | ||||||
|   const errors = []; |   const errors = []; | ||||||
|   await pReduce( |   await pReduce( | ||||||
| @ -33,7 +34,7 @@ module.exports = steps => async (input, {settleAll = false, getNextInput = ident | |||||||
|       let result; |       let result; | ||||||
|       try { |       try { | ||||||
|         // Call the step with the input computed at the end of the previous iteration and save intermediary result
 |         // Call the step with the input computed at the end of the previous iteration and save intermediary result
 | ||||||
|         result = await transform(await step(lastInput), step); |         result = await transform(await step(lastInput), step, lastInput); | ||||||
|         results.push(result); |         results.push(result); | ||||||
|       } catch (err) { |       } catch (err) { | ||||||
|         if (settleAll) { |         if (settleAll) { | ||||||
|  | |||||||
| @ -1,5 +1,6 @@ | |||||||
| import test from 'ava'; | import test from 'ava'; | ||||||
| import plugins from '../../lib/definitions/plugins'; | import plugins from '../../lib/definitions/plugins'; | ||||||
|  | import {RELEASE_NOTES_SEPARATOR} from '../../lib/definitions/constants'; | ||||||
| 
 | 
 | ||||||
| test('The "verifyConditions" plugin, if defined, must be a single or an array of plugins definition', t => { | test('The "verifyConditions" plugin, if defined, must be a single or an array of plugins definition', t => { | ||||||
|   t.false(plugins.verifyConditions.configValidator({})); |   t.false(plugins.verifyConditions.configValidator({})); | ||||||
| @ -118,3 +119,11 @@ test('The "publish" plugin output, if defined, must be an object', t => { | |||||||
|   t.true(plugins.publish.outputValidator(null)); |   t.true(plugins.publish.outputValidator(null)); | ||||||
|   t.true(plugins.publish.outputValidator('')); |   t.true(plugins.publish.outputValidator('')); | ||||||
| }); | }); | ||||||
|  | 
 | ||||||
|  | test('The "generateNotes" plugins output are concatenated with separator', t => { | ||||||
|  |   t.is(plugins.generateNotes.postprocess(['note 1', 'note 2']), `note 1${RELEASE_NOTES_SEPARATOR}note 2`); | ||||||
|  |   t.is(plugins.generateNotes.postprocess(['', 'note']), 'note'); | ||||||
|  |   t.is(plugins.generateNotes.postprocess([undefined, 'note']), 'note'); | ||||||
|  |   t.is(plugins.generateNotes.postprocess(['note 1', '', 'note 2']), `note 1${RELEASE_NOTES_SEPARATOR}note 2`); | ||||||
|  |   t.is(plugins.generateNotes.postprocess(['note 1', undefined, 'note 2']), `note 1${RELEASE_NOTES_SEPARATOR}note 2`); | ||||||
|  | }); | ||||||
|  | |||||||
| @ -25,7 +25,7 @@ test('Execute each function in series passing a transformed input from "getNextI | |||||||
|   const step4 = stub().resolves(4); |   const step4 = stub().resolves(4); | ||||||
|   const getNextInput = (lastResult, result) => lastResult + result; |   const getNextInput = (lastResult, result) => lastResult + result; | ||||||
| 
 | 
 | ||||||
|   const result = await pipeline([step1, step2, step3, step4])(0, {settleAll: false, getNextInput}); |   const result = await pipeline([step1, step2, step3, step4], {settleAll: false, getNextInput})(0); | ||||||
| 
 | 
 | ||||||
|   t.deepEqual(result, [1, 2, 3, 4]); |   t.deepEqual(result, [1, 2, 3, 4]); | ||||||
|   t.true(step1.calledWith(0)); |   t.true(step1.calledWith(0)); | ||||||
| @ -44,7 +44,7 @@ test('Execute each function in series passing the "lastResult" and "result" to " | |||||||
|   const step4 = stub().resolves(4); |   const step4 = stub().resolves(4); | ||||||
|   const getNextInput = stub().returnsArg(0); |   const getNextInput = stub().returnsArg(0); | ||||||
| 
 | 
 | ||||||
|   const result = await pipeline([step1, step2, step3, step4])(5, {settleAll: false, getNextInput}); |   const result = await pipeline([step1, step2, step3, step4], {settleAll: false, getNextInput})(5); | ||||||
| 
 | 
 | ||||||
|   t.deepEqual(result, [1, 2, 3, 4]); |   t.deepEqual(result, [1, 2, 3, 4]); | ||||||
|   t.deepEqual(getNextInput.args, [[5, 1], [5, 2], [5, 3], [5, 4]]); |   t.deepEqual(getNextInput.args, [[5, 1], [5, 2], [5, 3], [5, 4]]); | ||||||
| @ -58,7 +58,7 @@ test('Execute each function in series calling "transform" to modify the results' | |||||||
|   const getNextInput = stub().returnsArg(0); |   const getNextInput = stub().returnsArg(0); | ||||||
|   const transform = stub().callsFake(result => result + 1); |   const transform = stub().callsFake(result => result + 1); | ||||||
| 
 | 
 | ||||||
|   const result = await pipeline([step1, step2, step3, step4])(5, {getNextInput, transform}); |   const result = await pipeline([step1, step2, step3, step4], {getNextInput, transform})(5); | ||||||
| 
 | 
 | ||||||
|   t.deepEqual(result, [1 + 1, 2 + 1, 3 + 1, 4 + 1]); |   t.deepEqual(result, [1 + 1, 2 + 1, 3 + 1, 4 + 1]); | ||||||
|   t.deepEqual(getNextInput.args, [[5, 1 + 1], [5, 2 + 1], [5, 3 + 1], [5, 4 + 1]]); |   t.deepEqual(getNextInput.args, [[5, 1 + 1], [5, 2 + 1], [5, 3 + 1], [5, 4 + 1]]); | ||||||
| @ -72,7 +72,7 @@ test('Execute each function in series calling "transform" to modify the results | |||||||
|   const getNextInput = stub().returnsArg(0); |   const getNextInput = stub().returnsArg(0); | ||||||
|   const transform = stub().callsFake(result => result + 1); |   const transform = stub().callsFake(result => result + 1); | ||||||
| 
 | 
 | ||||||
|   const result = await pipeline([step1, step2, step3, step4])(5, {settleAll: true, getNextInput, transform}); |   const result = await pipeline([step1, step2, step3, step4], {settleAll: true, getNextInput, transform})(5); | ||||||
| 
 | 
 | ||||||
|   t.deepEqual(result, [1 + 1, 2 + 1, 3 + 1, 4 + 1]); |   t.deepEqual(result, [1 + 1, 2 + 1, 3 + 1, 4 + 1]); | ||||||
|   t.deepEqual(getNextInput.args, [[5, 1 + 1], [5, 2 + 1], [5, 3 + 1], [5, 4 + 1]]); |   t.deepEqual(getNextInput.args, [[5, 1 + 1], [5, 2 + 1], [5, 3 + 1], [5, 4 + 1]]); | ||||||
| @ -113,7 +113,7 @@ test('Execute all even if a Promise rejects', async t => { | |||||||
|   const step2 = stub().rejects(error1); |   const step2 = stub().rejects(error1); | ||||||
|   const step3 = stub().rejects(error2); |   const step3 = stub().rejects(error2); | ||||||
| 
 | 
 | ||||||
|   const errors = await t.throws(pipeline([step1, step2, step3])(0, {settleAll: true})); |   const errors = await t.throws(pipeline([step1, step2, step3], {settleAll: true})(0)); | ||||||
| 
 | 
 | ||||||
|   t.deepEqual([...errors], [error1, error2]); |   t.deepEqual([...errors], [error1, error2]); | ||||||
|   t.true(step1.calledWith(0)); |   t.true(step1.calledWith(0)); | ||||||
| @ -129,7 +129,7 @@ test('Throw all errors from all steps throwing an AggregateError', async t => { | |||||||
|   const step1 = stub().rejects(new AggregateError([error1, error2])); |   const step1 = stub().rejects(new AggregateError([error1, error2])); | ||||||
|   const step2 = stub().rejects(new AggregateError([error3, error4])); |   const step2 = stub().rejects(new AggregateError([error3, error4])); | ||||||
| 
 | 
 | ||||||
|   const errors = await t.throws(pipeline([step1, step2])(0, {settleAll: true})); |   const errors = await t.throws(pipeline([step1, step2], {settleAll: true})(0)); | ||||||
| 
 | 
 | ||||||
|   t.deepEqual([...errors], [error1, error2, error3, error4]); |   t.deepEqual([...errors], [error1, error2, error3, error4]); | ||||||
|   t.true(step1.calledWith(0)); |   t.true(step1.calledWith(0)); | ||||||
| @ -145,7 +145,7 @@ test('Execute each function in series passing a transformed input even if a step | |||||||
|   const step4 = stub().resolves(4); |   const step4 = stub().resolves(4); | ||||||
|   const getNextInput = (prevResult, result) => prevResult + result; |   const getNextInput = (prevResult, result) => prevResult + result; | ||||||
| 
 | 
 | ||||||
|   const errors = await t.throws(pipeline([step1, step2, step3, step4])(0, {settleAll: true, getNextInput})); |   const errors = await t.throws(pipeline([step1, step2, step3, step4], {settleAll: true, getNextInput})(0)); | ||||||
| 
 | 
 | ||||||
|   t.deepEqual([...errors], [error2, error3]); |   t.deepEqual([...errors], [error2, error3]); | ||||||
|   t.true(step1.calledWith(0)); |   t.true(step1.calledWith(0)); | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user