feat: log with signale and allow to customize stdin and stdout
				
					
				
			This commit is contained in:
		
							parent
							
								
									f64046f1d9
								
							
						
					
					
						commit
						0626d57116
					
				
							
								
								
									
										36
									
								
								index.js
									
									
									
									
									
								
							
							
						
						
									
										36
									
								
								index.js
									
									
									
									
									
								
							| @ -13,7 +13,7 @@ const getCommits = require('./lib/get-commits'); | |||||||
| const getLastRelease = require('./lib/get-last-release'); | const getLastRelease = require('./lib/get-last-release'); | ||||||
| const {extractErrors} = require('./lib/utils'); | const {extractErrors} = require('./lib/utils'); | ||||||
| const getGitAuthUrl = require('./lib/get-git-auth-url'); | const getGitAuthUrl = require('./lib/get-git-auth-url'); | ||||||
| const logger = require('./lib/logger'); | const getLogger = require('./lib/get-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} = require('./lib/definitions/constants'); | const {COMMIT_NAME, COMMIT_EMAIL} = require('./lib/definitions/constants'); | ||||||
| @ -53,6 +53,7 @@ async function run(context, plugins) { | |||||||
|     ); |     ); | ||||||
|     return false; |     return false; | ||||||
|   } |   } | ||||||
|  |   logger.success(`Run automated release from branch ${ciBranch}`); | ||||||
| 
 | 
 | ||||||
|   await verify(context); |   await verify(context); | ||||||
| 
 | 
 | ||||||
| @ -63,16 +64,15 @@ async function run(context, plugins) { | |||||||
|   } catch (err) { |   } catch (err) { | ||||||
|     if (!(await isBranchUpToDate(options.branch, {cwd, env}))) { |     if (!(await isBranchUpToDate(options.branch, {cwd, env}))) { | ||||||
|       logger.log( |       logger.log( | ||||||
|         "The local branch %s is behind the remote one, therefore a new version won't be published.", |         `The local branch ${options.branch} is behind the remote one, therefore a new version won't be published.` | ||||||
|         options.branch |  | ||||||
|       ); |       ); | ||||||
|       return false; |       return false; | ||||||
|     } |     } | ||||||
|     logger.error(`The command "${err.cmd}" failed with the error message %s.`, err.stderr); |     logger.error(`The command "${err.cmd}" failed with the error message ${err.stderr}.`); | ||||||
|     throw getError('EGITNOPERMISSION', {options}); |     throw getError('EGITNOPERMISSION', {options}); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   logger.log('Run automated release from branch %s', options.branch); |   logger.success(`Allowed to push to the Git repository`); | ||||||
| 
 | 
 | ||||||
|   await plugins.verifyConditions(context); |   await plugins.verifyConditions(context); | ||||||
| 
 | 
 | ||||||
| @ -95,35 +95,35 @@ async function run(context, plugins) { | |||||||
| 
 | 
 | ||||||
|   if (options.dryRun) { |   if (options.dryRun) { | ||||||
|     const notes = await plugins.generateNotes(context); |     const notes = await plugins.generateNotes(context); | ||||||
|     logger.log('Release note for version %s:\n', nextRelease.version); |     logger.log(`Release note for version ${nextRelease.version}:`); | ||||||
|     if (notes) { |     if (notes) { | ||||||
|       logger.stdout(`${marked(notes)}\n`); |       context.stdout.write(marked(notes)); | ||||||
|     } |     } | ||||||
|   } else { |   } else { | ||||||
|     nextRelease.notes = await plugins.generateNotes(context); |     nextRelease.notes = await plugins.generateNotes(context); | ||||||
|     await plugins.prepare(context); |     await plugins.prepare(context); | ||||||
| 
 | 
 | ||||||
|     // 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); |  | ||||||
|     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}`); | ||||||
| 
 | 
 | ||||||
|     context.releases = await plugins.publish(context); |     context.releases = await plugins.publish(context); | ||||||
| 
 | 
 | ||||||
|     await plugins.success(context); |     await plugins.success(context); | ||||||
| 
 | 
 | ||||||
|     logger.log('Published release: %s', nextRelease.version); |     logger.success(`Published release ${nextRelease.version}`); | ||||||
|   } |   } | ||||||
|   return true; |   return true; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function logErrors({logger}, err) { | function logErrors({logger, stderr}, err) { | ||||||
|   const errors = extractErrors(err).sort(error => (error.semanticRelease ? -1 : 0)); |   const errors = extractErrors(err).sort(error => (error.semanticRelease ? -1 : 0)); | ||||||
|   for (const error of errors) { |   for (const error of errors) { | ||||||
|     if (error.semanticRelease) { |     if (error.semanticRelease) { | ||||||
|       logger.log(`%s ${error.message}`, error.code); |       logger.error(`${error.code} ${error.message}`); | ||||||
|       if (error.details) { |       if (error.details) { | ||||||
|         logger.stderr(`${marked(error.details)}\n`); |         stderr.write(marked(error.details)); | ||||||
|       } |       } | ||||||
|     } else { |     } else { | ||||||
|       logger.error('An error occurred while running semantic-release: %O', error); |       logger.error('An error occurred while running semantic-release: %O', error); | ||||||
| @ -142,10 +142,14 @@ async function callFail(context, plugins, error) { | |||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| module.exports = async (opts, {cwd = process.cwd(), env = process.env} = {}) => { | module.exports = async (opts = {}, {cwd = process.cwd(), env = process.env, stdout, stderr} = {}) => { | ||||||
|   const context = {cwd, env, logger}; |   const {unhook} = hookStd( | ||||||
|   context.logger.log(`Running %s version %s`, pkg.name, pkg.version); |     {silent: false, streams: [process.stdout, process.stderr, stdout, stderr].filter(Boolean)}, | ||||||
|   const {unhook} = hookStd({silent: false}, hideSensitive(context.env)); |     hideSensitive(env) | ||||||
|  |   ); | ||||||
|  |   const context = {cwd, env, stdout: stdout || process.stdout, stderr: stderr || process.stderr}; | ||||||
|  |   context.logger = getLogger(context); | ||||||
|  |   context.logger.log(`Running ${pkg.name} version ${pkg.version}`); | ||||||
|   try { |   try { | ||||||
|     const {plugins, options} = await getConfig(context, opts); |     const {plugins, options} = await getConfig(context, opts); | ||||||
|     context.options = options; |     context.options = options; | ||||||
|  | |||||||
| @ -24,7 +24,7 @@ module.exports = async ({cwd, env, lastRelease: {gitHead}, logger}) => { | |||||||
|     commit.gitTags = commit.gitTags.trim(); |     commit.gitTags = commit.gitTags.trim(); | ||||||
|     return commit; |     return commit; | ||||||
|   }); |   }); | ||||||
|   logger.log('Found %s commits since last release', commits.length); |   logger.log(`Found ${commits.length} commits since last release`); | ||||||
|   debug('Parsed commits: %o', commits); |   debug('Parsed commits: %o', commits); | ||||||
|   return commits; |   return commits; | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -42,7 +42,7 @@ module.exports = async ({cwd, env, options: {tagFormat}, logger}) => { | |||||||
|   const tag = await pLocate(tags, tag => isRefInHistory(tag.gitTag, {cwd, env}), {preserveOrder: true}); |   const tag = await pLocate(tags, tag => isRefInHistory(tag.gitTag, {cwd, env}), {preserveOrder: true}); | ||||||
| 
 | 
 | ||||||
|   if (tag) { |   if (tag) { | ||||||
|     logger.log('Found git tag %s associated with version %s', tag.gitTag, tag.version); |     logger.log(`Found git tag ${tag.gitTag} associated with version ${tag.version}`); | ||||||
|     return {gitHead: await gitTagHead(tag.gitTag, {cwd, env}), ...tag}; |     return {gitHead: await gitTagHead(tag.gitTag, {cwd, env}), ...tag}; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										16
									
								
								lib/get-logger.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								lib/get-logger.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,16 @@ | |||||||
|  | const {Signale} = require('signale'); | ||||||
|  | const figures = require('figures'); | ||||||
|  | 
 | ||||||
|  | module.exports = ({stdout, stderr}) => | ||||||
|  |   new Signale({ | ||||||
|  |     config: {displayTimestamp: true, underlineMessage: true, displayLabel: false}, | ||||||
|  |     disabled: false, | ||||||
|  |     interactive: false, | ||||||
|  |     scope: 'semantic-release', | ||||||
|  |     stream: [stdout], | ||||||
|  |     types: { | ||||||
|  |       error: {badge: figures.cross, color: 'red', label: '', stream: [stderr]}, | ||||||
|  |       log: {badge: figures.info, color: 'magenta', label: '', stream: [stdout]}, | ||||||
|  |       success: {badge: figures.tick, color: 'green', label: '', stream: [stdout]}, | ||||||
|  |     }, | ||||||
|  |   }); | ||||||
| @ -5,10 +5,10 @@ module.exports = ({nextRelease: {type}, lastRelease, logger}) => { | |||||||
|   let version; |   let version; | ||||||
|   if (lastRelease.version) { |   if (lastRelease.version) { | ||||||
|     version = semver.inc(lastRelease.version, type); |     version = semver.inc(lastRelease.version, type); | ||||||
|     logger.log('The next release version is %s', version); |     logger.log(`The next release version is ${version}`); | ||||||
|   } else { |   } else { | ||||||
|     version = FIRST_RELEASE; |     version = FIRST_RELEASE; | ||||||
|     logger.log('There is no previous release, the next release version is %s', version); |     logger.log(`There is no previous release, the next release version is ${version}`); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   return version; |   return version; | ||||||
|  | |||||||
| @ -1,29 +0,0 @@ | |||||||
| const chalk = require('chalk'); |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * Logger with `log` and `error` function. |  | ||||||
|  */ |  | ||||||
| module.exports = { |  | ||||||
|   log(...args) { |  | ||||||
|     const [format, ...rest] = args; |  | ||||||
|     console.log( |  | ||||||
|       `${chalk.grey('[Semantic release]:')}${ |  | ||||||
|         typeof format === 'string' ? ` ${format.replace(/%[^%]/g, seq => chalk.magenta(seq))}` : '' |  | ||||||
|       }`,
 |  | ||||||
|       ...(typeof format === 'string' ? [] : [format]).concat(rest) |  | ||||||
|     ); |  | ||||||
|   }, |  | ||||||
|   error(...args) { |  | ||||||
|     const [format, ...rest] = args; |  | ||||||
|     console.error( |  | ||||||
|       `${chalk.grey('[Semantic release]:')}${typeof format === 'string' ? ` ${chalk.red(format)}` : ''}`, |  | ||||||
|       ...(typeof format === 'string' ? [] : [format]).concat(rest) |  | ||||||
|     ); |  | ||||||
|   }, |  | ||||||
|   stdout(...args) { |  | ||||||
|     console.log(args); |  | ||||||
|   }, |  | ||||||
|   stderr(...args) { |  | ||||||
|     console.error(args); |  | ||||||
|   }, |  | ||||||
| }; |  | ||||||
| @ -13,14 +13,6 @@ module.exports = ({cwd, options, logger}, type, pluginOpt, pluginsPath) => { | |||||||
|   const {path, ...config} = isString(pluginOpt) || isFunction(pluginOpt) ? {path: pluginOpt} : pluginOpt; |   const {path, ...config} = isString(pluginOpt) || isFunction(pluginOpt) ? {path: pluginOpt} : pluginOpt; | ||||||
|   const pluginName = isFunction(path) ? `[Function: ${path.name}]` : path; |   const pluginName = isFunction(path) ? `[Function: ${path.name}]` : path; | ||||||
| 
 | 
 | ||||||
|   if (!isFunction(pluginOpt)) { |  | ||||||
|     if (pluginsPath[path]) { |  | ||||||
|       logger.log('Load plugin "%s" from %s in shareable config %s', type, path, pluginsPath[path]); |  | ||||||
|     } else { |  | ||||||
|       logger.log('Load plugin "%s" from %s', type, path); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   const basePath = pluginsPath[path] |   const basePath = pluginsPath[path] | ||||||
|     ? dirname(resolveFrom.silent(__dirname, pluginsPath[path]) || resolveFrom(cwd, pluginsPath[path])) |     ? dirname(resolveFrom.silent(__dirname, pluginsPath[path]) || resolveFrom(cwd, pluginsPath[path])) | ||||||
|     : __dirname; |     : __dirname; | ||||||
| @ -38,18 +30,29 @@ module.exports = ({cwd, options, logger}, type, pluginOpt, pluginsPath) => { | |||||||
|   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); |       logger.log(`Start step "${type}" of plugin "${pluginName}"`); | ||||||
|       const result = await func(cloneDeep(input)); |       const result = await func({...cloneDeep(input), logger: logger.scope(logger.scopeName, pluginName)}); | ||||||
|       if (outputValidator && !outputValidator(result)) { |       if (outputValidator && !outputValidator(result)) { | ||||||
|         throw getError(`E${type.toUpperCase()}OUTPUT`, {result, pluginName}); |         throw getError(`E${type.toUpperCase()}OUTPUT`, {result, pluginName}); | ||||||
|       } |       } | ||||||
|  |       logger.success(`Completed step "${type}" of plugin "${pluginName}"`); | ||||||
|       return result; |       return result; | ||||||
|     } catch (err) { |     } catch (err) { | ||||||
|  |       logger.error(`Failed step "${type}" of plugin "${pluginName}"`); | ||||||
|       extractErrors(err).forEach(err => Object.assign(err, {pluginName})); |       extractErrors(err).forEach(err => Object.assign(err, {pluginName})); | ||||||
|       throw err; |       throw err; | ||||||
|     } |     } | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   Reflect.defineProperty(validator, 'pluginName', {value: pluginName, writable: false, enumerable: true}); |   Reflect.defineProperty(validator, 'pluginName', {value: pluginName, writable: false, enumerable: true}); | ||||||
|  | 
 | ||||||
|  |   if (!isFunction(pluginOpt)) { | ||||||
|  |     if (pluginsPath[path]) { | ||||||
|  |       logger.success(`Loaded plugin "${type}" from "${path}" in shareable config "${pluginsPath[path]}"`); | ||||||
|  |     } else { | ||||||
|  |       logger.success(`Loaded plugin "${type}" from "${path}"`); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   return validator; |   return validator; | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -22,19 +22,19 @@ | |||||||
|     "@semantic-release/commit-analyzer": "^6.0.0", |     "@semantic-release/commit-analyzer": "^6.0.0", | ||||||
|     "@semantic-release/error": "^2.2.0", |     "@semantic-release/error": "^2.2.0", | ||||||
|     "@semantic-release/github": "^5.0.0", |     "@semantic-release/github": "^5.0.0", | ||||||
|     "@semantic-release/npm": "^4.0.0", |     "@semantic-release/npm": "^4.0.1", | ||||||
|     "@semantic-release/release-notes-generator": "^7.0.0", |     "@semantic-release/release-notes-generator": "^7.0.0", | ||||||
|     "aggregate-error": "^1.0.0", |     "aggregate-error": "^1.0.0", | ||||||
|     "chalk": "^2.3.0", |  | ||||||
|     "cosmiconfig": "^5.0.1", |     "cosmiconfig": "^5.0.1", | ||||||
|     "debug": "^3.1.0", |     "debug": "^3.1.0", | ||||||
|     "env-ci": "^2.0.0", |     "env-ci": "^2.0.0", | ||||||
|     "execa": "^0.10.0", |     "execa": "^0.10.0", | ||||||
|  |     "figures": "^2.0.0", | ||||||
|     "find-versions": "^2.0.0", |     "find-versions": "^2.0.0", | ||||||
|     "get-stream": "^3.0.0", |     "get-stream": "^3.0.0", | ||||||
|     "git-log-parser": "^1.2.0", |     "git-log-parser": "^1.2.0", | ||||||
|     "git-url-parse": "^10.0.1", |     "git-url-parse": "^10.0.1", | ||||||
|     "hook-std": "^1.0.1", |     "hook-std": "^1.1.0", | ||||||
|     "hosted-git-info": "^2.7.1", |     "hosted-git-info": "^2.7.1", | ||||||
|     "lodash": "^4.17.4", |     "lodash": "^4.17.4", | ||||||
|     "marked": "^0.4.0", |     "marked": "^0.4.0", | ||||||
| @ -44,6 +44,7 @@ | |||||||
|     "read-pkg-up": "^4.0.0", |     "read-pkg-up": "^4.0.0", | ||||||
|     "resolve-from": "^4.0.0", |     "resolve-from": "^4.0.0", | ||||||
|     "semver": "^5.4.1", |     "semver": "^5.4.1", | ||||||
|  |     "signale": "^1.2.1", | ||||||
|     "yargs": "^12.0.0" |     "yargs": "^12.0.0" | ||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|  | |||||||
| @ -27,7 +27,7 @@ test('Get the highest non-prerelease valid tag', async t => { | |||||||
|   const result = await getLastRelease({cwd, options: {tagFormat: `v\${version}`}, logger: t.context.logger}); |   const result = await getLastRelease({cwd, options: {tagFormat: `v\${version}`}, logger: t.context.logger}); | ||||||
| 
 | 
 | ||||||
|   t.deepEqual(result, {gitHead: commits[0].hash, gitTag: 'v2.0.0', version: '2.0.0'}); |   t.deepEqual(result, {gitHead: commits[0].hash, gitTag: 'v2.0.0', version: '2.0.0'}); | ||||||
|   t.deepEqual(t.context.log.args[0], ['Found git tag %s associated with version %s', 'v2.0.0', '2.0.0']); |   t.deepEqual(t.context.log.args[0], ['Found git tag v2.0.0 associated with version 2.0.0']); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| test('Get the highest tag in the history of the current branch', async t => { | test('Get the highest tag in the history of the current branch', async t => { | ||||||
|  | |||||||
							
								
								
									
										17
									
								
								test/get-logger.test.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								test/get-logger.test.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | |||||||
|  | import test from 'ava'; | ||||||
|  | import {spy} from 'sinon'; | ||||||
|  | import getLogger from '../lib/get-logger'; | ||||||
|  | 
 | ||||||
|  | test('Expose "error", "success" and "log" functions', t => { | ||||||
|  |   const stdout = spy(); | ||||||
|  |   const stderr = spy(); | ||||||
|  |   const logger = getLogger({stdout: {write: stdout}, stderr: {write: stderr}}); | ||||||
|  | 
 | ||||||
|  |   logger.log('test log'); | ||||||
|  |   logger.success('test success'); | ||||||
|  |   logger.error('test error'); | ||||||
|  | 
 | ||||||
|  |   t.regex(stdout.args[0][0], /.*test log/); | ||||||
|  |   t.regex(stdout.args[1][0], /.*test success/); | ||||||
|  |   t.regex(stderr.args[0][0], /.*test error/); | ||||||
|  | }); | ||||||
| @ -22,9 +22,13 @@ test.beforeEach(t => { | |||||||
|   // Stub the logger functions
 |   // Stub the logger functions
 | ||||||
|   t.context.log = spy(); |   t.context.log = spy(); | ||||||
|   t.context.error = spy(); |   t.context.error = spy(); | ||||||
|   t.context.stdout = spy(); |   t.context.success = spy(); | ||||||
|   t.context.stderr = spy(); |   t.context.logger = { | ||||||
|   t.context.logger = {log: t.context.log, error: t.context.error, stdout: t.context.stdout, stderr: t.context.stderr}; |     log: t.context.log, | ||||||
|  |     error: t.context.error, | ||||||
|  |     success: t.context.success, | ||||||
|  |     scope: () => t.context.logger, | ||||||
|  |   }; | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| test('Plugins are called with expected values', async t => { | test('Plugins are called with expected values', async t => { | ||||||
| @ -68,10 +72,10 @@ test('Plugins are called with expected values', async t => { | |||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   const semanticRelease = requireNoCache('..', { |   const semanticRelease = requireNoCache('..', { | ||||||
|     './lib/logger': t.context.logger, |     './lib/get-logger': () => t.context.logger, | ||||||
|     'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), |     'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), | ||||||
|   }); |   }); | ||||||
|   t.truthy(await semanticRelease(options, {cwd, extendEnv: false, env})); |   t.truthy(await semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}})); | ||||||
| 
 | 
 | ||||||
|   t.is(verifyConditions1.callCount, 1); |   t.is(verifyConditions1.callCount, 1); | ||||||
|   t.deepEqual(verifyConditions1.args[0][0], config); |   t.deepEqual(verifyConditions1.args[0][0], config); | ||||||
| @ -193,10 +197,10 @@ test('Use custom tag format', async t => { | |||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   const semanticRelease = requireNoCache('..', { |   const semanticRelease = requireNoCache('..', { | ||||||
|     './lib/logger': t.context.logger, |     './lib/get-logger': () => t.context.logger, | ||||||
|     'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), |     'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), | ||||||
|   }); |   }); | ||||||
|   t.truthy(await semanticRelease(options, {cwd, env: {}})); |   t.truthy(await semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}})); | ||||||
| 
 | 
 | ||||||
|   // Verify the tag has been created on the local and remote repo and reference the gitHead
 |   // Verify the tag has been created on the local and remote repo and reference the gitHead
 | ||||||
|   t.is(await gitTagHead(nextRelease.gitTag, {cwd}), nextRelease.gitHead); |   t.is(await gitTagHead(nextRelease.gitTag, {cwd}), nextRelease.gitHead); | ||||||
| @ -237,11 +241,11 @@ test('Use new gitHead, and recreate release notes if a prepare plugin create a c | |||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   const semanticRelease = requireNoCache('..', { |   const semanticRelease = requireNoCache('..', { | ||||||
|     './lib/logger': t.context.logger, |     './lib/get-logger': () => t.context.logger, | ||||||
|     'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), |     'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   t.truthy(await semanticRelease(options, {cwd, env: {}})); |   t.truthy(await semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}})); | ||||||
| 
 | 
 | ||||||
|   t.is(generateNotes.callCount, 2); |   t.is(generateNotes.callCount, 2); | ||||||
|   t.deepEqual(generateNotes.args[0][1].nextRelease, nextRelease); |   t.deepEqual(generateNotes.args[0][1].nextRelease, nextRelease); | ||||||
| @ -295,11 +299,11 @@ test('Call all "success" plugins even if one errors out', async t => { | |||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   const semanticRelease = requireNoCache('..', { |   const semanticRelease = requireNoCache('..', { | ||||||
|     './lib/logger': t.context.logger, |     './lib/get-logger': () => t.context.logger, | ||||||
|     'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), |     'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   await t.throws(semanticRelease(options, {cwd, env: {}})); |   await t.throws(semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}})); | ||||||
| 
 | 
 | ||||||
|   t.is(success1.callCount, 1); |   t.is(success1.callCount, 1); | ||||||
|   t.deepEqual(success1.args[0][1].releases, [{...release, ...nextRelease, notes, pluginName: '[Function: proxy]'}]); |   t.deepEqual(success1.args[0][1].releases, [{...release, ...nextRelease, notes, pluginName: '[Function: proxy]'}]); | ||||||
| @ -327,15 +331,17 @@ test('Log all "verifyConditions" errors', async t => { | |||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   const semanticRelease = requireNoCache('..', { |   const semanticRelease = requireNoCache('..', { | ||||||
|     './lib/logger': t.context.logger, |     './lib/get-logger': () => t.context.logger, | ||||||
|     'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), |     'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), | ||||||
|   }); |   }); | ||||||
|   const errors = [...(await t.throws(semanticRelease(options, {cwd, env: {}})))]; |   const errors = [ | ||||||
|  |     ...(await t.throws(semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}}))), | ||||||
|  |   ]; | ||||||
| 
 | 
 | ||||||
|   t.deepEqual(errors, [error1, error2, error3]); |   t.deepEqual(errors, [error1, error2, error3]); | ||||||
|   t.deepEqual(t.context.log.args[t.context.log.args.length - 2], ['%s error 2', 'ERR2']); |   t.deepEqual(t.context.error.args[t.context.error.args.length - 2], ['ERR2 error 2']); | ||||||
|   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], ['ERR3 error 3']); | ||||||
|   t.deepEqual(t.context.error.args[t.context.error.args.length - 1], [ |   t.deepEqual(t.context.error.args[t.context.error.args.length - 3], [ | ||||||
|     'An error occurred while running semantic-release: %O', |     'An error occurred while running semantic-release: %O', | ||||||
|     error1, |     error1, | ||||||
|   ]); |   ]); | ||||||
| @ -371,14 +377,16 @@ test('Log all "verifyRelease" errors', async t => { | |||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   const semanticRelease = requireNoCache('..', { |   const semanticRelease = requireNoCache('..', { | ||||||
|     './lib/logger': t.context.logger, |     './lib/get-logger': () => t.context.logger, | ||||||
|     'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), |     'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), | ||||||
|   }); |   }); | ||||||
|   const errors = [...(await t.throws(semanticRelease(options, {cwd, env: {}})))]; |   const errors = [ | ||||||
|  |     ...(await t.throws(semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}}))), | ||||||
|  |   ]; | ||||||
| 
 | 
 | ||||||
|   t.deepEqual(errors, [error1, error2]); |   t.deepEqual(errors, [error1, error2]); | ||||||
|   t.deepEqual(t.context.log.args[t.context.log.args.length - 2], ['%s error 1', 'ERR1']); |   t.deepEqual(t.context.error.args[t.context.error.args.length - 2], ['ERR1 error 1']); | ||||||
|   t.deepEqual(t.context.log.args[t.context.log.args.length - 1], ['%s error 2', 'ERR2']); |   t.deepEqual(t.context.error.args[t.context.error.args.length - 1], ['ERR2 error 2']); | ||||||
|   t.is(fail.callCount, 1); |   t.is(fail.callCount, 1); | ||||||
|   t.deepEqual(fail.args[0][0], config); |   t.deepEqual(fail.args[0][0], config); | ||||||
|   t.deepEqual(fail.args[0][1].errors, [error1, error2]); |   t.deepEqual(fail.args[0][1].errors, [error1, error2]); | ||||||
| @ -419,10 +427,10 @@ test('Dry-run skips publish and success', async t => { | |||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   const semanticRelease = requireNoCache('..', { |   const semanticRelease = requireNoCache('..', { | ||||||
|     './lib/logger': t.context.logger, |     './lib/get-logger': () => t.context.logger, | ||||||
|     'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), |     'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), | ||||||
|   }); |   }); | ||||||
|   t.truthy(await semanticRelease(options, {cwd, env: {}})); |   t.truthy(await semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}})); | ||||||
| 
 | 
 | ||||||
|   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.log.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); | ||||||
| @ -457,14 +465,16 @@ test('Dry-run skips fail', async t => { | |||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   const semanticRelease = requireNoCache('..', { |   const semanticRelease = requireNoCache('..', { | ||||||
|     './lib/logger': t.context.logger, |     './lib/get-logger': () => t.context.logger, | ||||||
|     'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), |     'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), | ||||||
|   }); |   }); | ||||||
|   const errors = [...(await t.throws(semanticRelease(options, {cwd, env: {}})))]; |   const errors = [ | ||||||
|  |     ...(await t.throws(semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}}))), | ||||||
|  |   ]; | ||||||
| 
 | 
 | ||||||
|   t.deepEqual(errors, [error1, error2]); |   t.deepEqual(errors, [error1, error2]); | ||||||
|   t.deepEqual(t.context.log.args[t.context.log.args.length - 2], ['%s error 1', 'ERR1']); |   t.deepEqual(t.context.error.args[t.context.error.args.length - 2], ['ERR1 error 1']); | ||||||
|   t.deepEqual(t.context.log.args[t.context.log.args.length - 1], ['%s error 2', 'ERR2']); |   t.deepEqual(t.context.error.args[t.context.error.args.length - 1], ['ERR2 error 2']); | ||||||
|   t.is(fail.callCount, 0); |   t.is(fail.callCount, 0); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| @ -504,10 +514,10 @@ test('Force a dry-run if not on a CI and "noCi" is not explicitly set', async t | |||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   const semanticRelease = requireNoCache('..', { |   const semanticRelease = requireNoCache('..', { | ||||||
|     './lib/logger': t.context.logger, |     './lib/get-logger': () => t.context.logger, | ||||||
|     'env-ci': () => ({isCi: false, branch: 'master'}), |     'env-ci': () => ({isCi: false, branch: 'master'}), | ||||||
|   }); |   }); | ||||||
|   t.truthy(await semanticRelease(options, {cwd, env: {}})); |   t.truthy(await semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}})); | ||||||
| 
 | 
 | ||||||
|   t.is(t.context.log.args[1][0], 'This run was not triggered in a known CI environment, running in dry-run mode.'); |   t.is(t.context.log.args[1][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); | ||||||
| @ -518,7 +528,7 @@ test('Force a dry-run if not on a CI and "noCi" is not explicitly set', async t | |||||||
|   t.is(success.callCount, 0); |   t.is(success.callCount, 0); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| test.serial('Dry-run does not print changelog if "generateNotes" return "undefined"', async t => { | test('Dry-run does not print changelog if "generateNotes" return "undefined"', 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
 | ||||||
|   const {cwd, repositoryUrl} = await gitRepo(true); |   const {cwd, repositoryUrl} = await gitRepo(true); | ||||||
|   // Add commits to the master branch
 |   // Add commits to the master branch
 | ||||||
| @ -547,12 +557,12 @@ test.serial('Dry-run does not print changelog if "generateNotes" return "undefin | |||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   const semanticRelease = requireNoCache('..', { |   const semanticRelease = requireNoCache('..', { | ||||||
|     './lib/logger': t.context.logger, |     './lib/get-logger': () => t.context.logger, | ||||||
|     'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), |     'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), | ||||||
|   }); |   }); | ||||||
|   t.truthy(await semanticRelease(options, {cwd, env: {}})); |   t.truthy(await semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}})); | ||||||
| 
 | 
 | ||||||
|   t.deepEqual(t.context.log.args[t.context.log.args.length - 1], ['Release note for version %s:\n', '2.0.0']); |   t.deepEqual(t.context.log.args[t.context.log.args.length - 1], ['Release note for version 2.0.0:']); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| test('Allow local releases with "noCi" option', async t => { | test('Allow local releases with "noCi" option', async t => { | ||||||
| @ -591,10 +601,10 @@ test('Allow local releases with "noCi" option', async t => { | |||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   const semanticRelease = requireNoCache('..', { |   const semanticRelease = requireNoCache('..', { | ||||||
|     './lib/logger': t.context.logger, |     './lib/get-logger': () => t.context.logger, | ||||||
|     'env-ci': () => ({isCi: false, branch: 'master', isPr: true}), |     'env-ci': () => ({isCi: false, branch: 'master', isPr: true}), | ||||||
|   }); |   }); | ||||||
|   t.truthy(await semanticRelease(options, {cwd, env: {}})); |   t.truthy(await semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}})); | ||||||
| 
 | 
 | ||||||
|   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.log.args[0][0], 'This run was not triggered in a known CI environment, running in dry-run mode.'); | ||||||
|   t.not( |   t.not( | ||||||
| @ -643,10 +653,10 @@ test('Accept "undefined" value returned by the "generateNotes" plugins', async t | |||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   const semanticRelease = requireNoCache('..', { |   const semanticRelease = requireNoCache('..', { | ||||||
|     './lib/logger': t.context.logger, |     './lib/get-logger': () => t.context.logger, | ||||||
|     'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), |     'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), | ||||||
|   }); |   }); | ||||||
|   t.truthy(await semanticRelease(options, {cwd, env: {}})); |   t.truthy(await semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}})); | ||||||
| 
 | 
 | ||||||
|   t.is(analyzeCommits.callCount, 1); |   t.is(analyzeCommits.callCount, 1); | ||||||
|   t.deepEqual(analyzeCommits.args[0][1].lastRelease, lastRelease); |   t.deepEqual(analyzeCommits.args[0][1].lastRelease, lastRelease); | ||||||
| @ -670,11 +680,13 @@ test('Returns falsy value if triggered by a PR', async t => { | |||||||
|   const {cwd, repositoryUrl} = await gitRepo(true); |   const {cwd, repositoryUrl} = await gitRepo(true); | ||||||
| 
 | 
 | ||||||
|   const semanticRelease = requireNoCache('..', { |   const semanticRelease = requireNoCache('..', { | ||||||
|     './lib/logger': t.context.logger, |     './lib/get-logger': () => t.context.logger, | ||||||
|     'env-ci': () => ({isCi: true, branch: 'master', isPr: true}), |     'env-ci': () => ({isCi: true, branch: 'master', isPr: true}), | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   t.falsy(await semanticRelease({cwd, repositoryUrl}, {cwd, env: {}})); |   t.falsy( | ||||||
|  |     await semanticRelease({cwd, repositoryUrl}, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}}) | ||||||
|  |   ); | ||||||
|   t.is( |   t.is( | ||||||
|     t.context.log.args[t.context.log.args.length - 1][0], |     t.context.log.args[t.context.log.args.length - 1][0], | ||||||
|     "This run was triggered by a pull request and therefore a new version won't be published." |     "This run was triggered by a pull request and therefore a new version won't be published." | ||||||
| @ -694,18 +706,22 @@ test('Returns falsy value if triggered on an outdated clone', async t => { | |||||||
|   await gitPush(repositoryUrl, 'master', {cwd}); |   await gitPush(repositoryUrl, 'master', {cwd}); | ||||||
| 
 | 
 | ||||||
|   const semanticRelease = requireNoCache('..', { |   const semanticRelease = requireNoCache('..', { | ||||||
|     './lib/logger': t.context.logger, |     './lib/get-logger': () => t.context.logger, | ||||||
|     'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), |     'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   t.falsy(await semanticRelease({repositoryUrl}, {cwd: repoDir, env: {}})); |   t.falsy( | ||||||
|  |     await semanticRelease( | ||||||
|  |       {repositoryUrl}, | ||||||
|  |       {cwd: repoDir, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}} | ||||||
|  |     ) | ||||||
|  |   ); | ||||||
|   t.deepEqual(t.context.log.args[t.context.log.args.length - 1], [ |   t.deepEqual(t.context.log.args[t.context.log.args.length - 1], [ | ||||||
|     "The local branch %s is behind the remote one, therefore a new version won't be published.", |     "The local branch master is behind the remote one, therefore a new version won't be published.", | ||||||
|     'master', |  | ||||||
|   ]); |   ]); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| test('Returns falsy value if not running from the configured branch', async t => { | test('Returns false if not running from the configured branch', 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
 | ||||||
|   const {cwd, repositoryUrl} = await gitRepo(true); |   const {cwd, repositoryUrl} = await gitRepo(true); | ||||||
|   const options = { |   const options = { | ||||||
| @ -722,11 +738,11 @@ test('Returns falsy value if not running from the configured branch', async t => | |||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   const semanticRelease = requireNoCache('..', { |   const semanticRelease = requireNoCache('..', { | ||||||
|     './lib/logger': t.context.logger, |     './lib/get-logger': () => t.context.logger, | ||||||
|     'env-ci': () => ({isCi: true, branch: 'other-branch', isPr: false}), |     'env-ci': () => ({isCi: true, branch: 'other-branch', isPr: false}), | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   t.falsy(await semanticRelease(options, {cwd, env: {}})); |   t.falsy(await semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}})); | ||||||
|   t.is( |   t.is( | ||||||
|     t.context.log.args[1][0], |     t.context.log.args[1][0], | ||||||
|     'This test run was triggered on the branch other-branch, while semantic-release is configured to only publish from master, therefore a new version won’t be published.' |     'This test run was triggered on the branch other-branch, while semantic-release is configured to only publish from master, therefore a new version won’t be published.' | ||||||
| @ -759,11 +775,11 @@ test('Returns falsy value if there is no relevant changes', async t => { | |||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   const semanticRelease = requireNoCache('..', { |   const semanticRelease = requireNoCache('..', { | ||||||
|     './lib/logger': t.context.logger, |     './lib/get-logger': () => t.context.logger, | ||||||
|     'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), |     'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   t.falsy(await semanticRelease(options, {cwd, env: {}})); |   t.falsy(await semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}})); | ||||||
|   t.is(analyzeCommits.callCount, 1); |   t.is(analyzeCommits.callCount, 1); | ||||||
|   t.is(verifyRelease.callCount, 0); |   t.is(verifyRelease.callCount, 0); | ||||||
|   t.is(generateNotes.callCount, 0); |   t.is(generateNotes.callCount, 0); | ||||||
| @ -807,10 +823,10 @@ test('Exclude commits with [skip release] or [release skip] from analysis', asyn | |||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   const semanticRelease = requireNoCache('..', { |   const semanticRelease = requireNoCache('..', { | ||||||
|     './lib/logger': t.context.logger, |     './lib/get-logger': () => t.context.logger, | ||||||
|     'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), |     'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), | ||||||
|   }); |   }); | ||||||
|   await semanticRelease(options, {cwd, env: {}}); |   await semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}}); | ||||||
| 
 | 
 | ||||||
|   t.is(analyzeCommits.callCount, 1); |   t.is(analyzeCommits.callCount, 1); | ||||||
|   t.is(analyzeCommits.args[0][1].commits.length, 2); |   t.is(analyzeCommits.args[0][1].commits.length, 2); | ||||||
| @ -830,15 +846,15 @@ test('Log both plugins errors and errors thrown by "fail" plugin', async t => { | |||||||
|     fail: [stub().rejects(failError1), stub().rejects(failError2)], |     fail: [stub().rejects(failError1), stub().rejects(failError2)], | ||||||
|   }; |   }; | ||||||
|   const semanticRelease = requireNoCache('..', { |   const semanticRelease = requireNoCache('..', { | ||||||
|     './lib/logger': t.context.logger, |     './lib/get-logger': () => t.context.logger, | ||||||
|     'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), |     'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   await t.throws(semanticRelease(options, {cwd, env: {}})); |   await t.throws(semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}})); | ||||||
| 
 | 
 | ||||||
|   t.is(t.context.error.args[t.context.error.args.length - 2][1], failError1); |   t.is(t.context.error.args[t.context.error.args.length - 1][0], 'ERR Plugin error'); | ||||||
|   t.is(t.context.error.args[t.context.error.args.length - 1][1], failError2); |   t.is(t.context.error.args[t.context.error.args.length - 3][1], failError1); | ||||||
|   t.deepEqual(t.context.log.args[t.context.log.args.length - 1], ['%s Plugin error', 'ERR']); |   t.is(t.context.error.args[t.context.error.args.length - 2][1], failError2); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| test('Call "fail" only if a plugin returns a SemanticReleaseError', async t => { | test('Call "fail" only if a plugin returns a SemanticReleaseError', async t => { | ||||||
| @ -853,11 +869,11 @@ test('Call "fail" only if a plugin returns a SemanticReleaseError', async t => { | |||||||
|     fail, |     fail, | ||||||
|   }; |   }; | ||||||
|   const semanticRelease = requireNoCache('..', { |   const semanticRelease = requireNoCache('..', { | ||||||
|     './lib/logger': t.context.logger, |     './lib/get-logger': () => t.context.logger, | ||||||
|     'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), |     'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   await t.throws(semanticRelease(options, {cwd, env: {}})); |   await t.throws(semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}})); | ||||||
| 
 | 
 | ||||||
|   t.true(fail.notCalled); |   t.true(fail.notCalled); | ||||||
|   t.is(t.context.error.args[t.context.error.args.length - 1][1], pluginError); |   t.is(t.context.error.args[t.context.error.args.length - 1][1], pluginError); | ||||||
| @ -868,10 +884,12 @@ test('Throw SemanticReleaseError if repositoryUrl is not set and cannot be found | |||||||
|   const {cwd} = await gitRepo(); |   const {cwd} = await gitRepo(); | ||||||
| 
 | 
 | ||||||
|   const semanticRelease = requireNoCache('..', { |   const semanticRelease = requireNoCache('..', { | ||||||
|     './lib/logger': t.context.logger, |     './lib/get-logger': () => t.context.logger, | ||||||
|     'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), |     'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), | ||||||
|   }); |   }); | ||||||
|   const errors = [...(await t.throws(semanticRelease({}, {cwd, env: {}})))]; |   const errors = [ | ||||||
|  |     ...(await t.throws(semanticRelease({}, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}}))), | ||||||
|  |   ]; | ||||||
| 
 | 
 | ||||||
|   // Verify error code and type
 |   // Verify error code and type
 | ||||||
|   t.is(errors[0].code, 'ENOREPOURL'); |   t.is(errors[0].code, 'ENOREPOURL'); | ||||||
| @ -902,10 +920,13 @@ test('Throw an Error if plugin returns an unexpected value', async t => { | |||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   const semanticRelease = requireNoCache('..', { |   const semanticRelease = requireNoCache('..', { | ||||||
|     './lib/logger': t.context.logger, |     './lib/get-logger': () => t.context.logger, | ||||||
|     'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), |     'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), | ||||||
|   }); |   }); | ||||||
|   const error = await t.throws(semanticRelease(options, {cwd, env: {}}), Error); |   const error = await t.throws( | ||||||
|  |     semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}}), | ||||||
|  |     Error | ||||||
|  |   ); | ||||||
|   t.regex(error.details, /string/); |   t.regex(error.details, /string/); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| @ -935,10 +956,10 @@ test('Get all commits including the ones not in the shallow clone', async t => { | |||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   const semanticRelease = requireNoCache('..', { |   const semanticRelease = requireNoCache('..', { | ||||||
|     './lib/logger': t.context.logger, |     './lib/get-logger': () => t.context.logger, | ||||||
|     'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), |     'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), | ||||||
|   }); |   }); | ||||||
|   t.truthy(await semanticRelease(options, {cwd, env: {}})); |   t.truthy(await semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}})); | ||||||
| 
 | 
 | ||||||
|   t.is(analyzeCommits.args[0][1].commits.length, 3); |   t.is(analyzeCommits.args[0][1].commits.length, 3); | ||||||
| }); | }); | ||||||
|  | |||||||
| @ -465,6 +465,10 @@ test('Run via JS API', async t => { | |||||||
|     version: '0.0.0-dev', |     version: '0.0.0-dev', | ||||||
|     repository: {url: repositoryUrl}, |     repository: {url: repositoryUrl}, | ||||||
|     publishConfig: {registry: npmRegistry.url}, |     publishConfig: {registry: npmRegistry.url}, | ||||||
|  |     release: { | ||||||
|  |       fail: false, | ||||||
|  |       success: false, | ||||||
|  |     }, | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   /* Initial release */ |   /* Initial release */ | ||||||
| @ -486,7 +490,7 @@ test('Run via JS API', async t => { | |||||||
|   t.log('Commit a feature'); |   t.log('Commit a feature'); | ||||||
|   await gitCommits(['feat: Initial commit'], {cwd}); |   await gitCommits(['feat: Initial commit'], {cwd}); | ||||||
|   t.log('$ Call semantic-release via API'); |   t.log('$ Call semantic-release via API'); | ||||||
|   await semanticRelease({fail: false, success: false}, {cwd, env}); |   await semanticRelease(undefined, {cwd, env, stdout: {write: () => {}}, stderr: {write: () => {}}}); | ||||||
| 
 | 
 | ||||||
|   // Verify package.json and has been updated
 |   // Verify package.json and has been updated
 | ||||||
|   t.is((await readJson(path.resolve(cwd, 'package.json'))).version, version); |   t.is((await readJson(path.resolve(cwd, 'package.json'))).version, version); | ||||||
| @ -550,9 +554,9 @@ test('Log errors inheriting SemanticReleaseError and exit with 1', async t => { | |||||||
|   t.log('Commit a feature'); |   t.log('Commit a feature'); | ||||||
|   await gitCommits(['feat: Initial commit'], {cwd}); |   await gitCommits(['feat: Initial commit'], {cwd}); | ||||||
|   t.log('$ semantic-release'); |   t.log('$ semantic-release'); | ||||||
|   const {stdout, code} = await execa(cli, [], {env, cwd, reject: false}); |   const {stderr, code} = await execa(cli, [], {env, cwd, reject: false}); | ||||||
|   // Verify the type and message are logged
 |   // Verify the type and message are logged
 | ||||||
|   t.regex(stdout, /EINHERITED Inherited error/); |   t.regex(stderr, /EINHERITED Inherited error/); | ||||||
|   t.is(code, 1); |   t.is(code, 1); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| @ -568,13 +572,13 @@ test('Exit with 1 if missing permission to push to the remote repository', async | |||||||
|   await gitCommits(['feat: Initial commit'], {cwd}); |   await gitCommits(['feat: Initial commit'], {cwd}); | ||||||
|   await gitPush('origin', 'master', {cwd}); |   await gitPush('origin', 'master', {cwd}); | ||||||
|   t.log('$ semantic-release'); |   t.log('$ semantic-release'); | ||||||
|   const {stdout, code} = await execa( |   const {stderr, code} = await execa( | ||||||
|     cli, |     cli, | ||||||
|     ['--repository-url', 'http://user:wrong_pass@localhost:2080/git/unauthorized.git'], |     ['--repository-url', 'http://user:wrong_pass@localhost:2080/git/unauthorized.git'], | ||||||
|     {env: {...env, GH_TOKEN: 'user:wrong_pass'}, cwd, reject: false} |     {env: {...env, GH_TOKEN: 'user:wrong_pass'}, cwd, reject: false} | ||||||
|   ); |   ); | ||||||
|   // Verify the type and message are logged
 |   // Verify the type and message are logged
 | ||||||
|   t.regex(stdout, /EGITNOPERMISSION/); |   t.regex(stderr, /EGITNOPERMISSION/); | ||||||
|   t.is(code, 1); |   t.is(code, 1); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,51 +0,0 @@ | |||||||
| import test from 'ava'; |  | ||||||
| import {stub} from 'sinon'; |  | ||||||
| import logger from '../lib/logger'; |  | ||||||
| 
 |  | ||||||
| test.beforeEach(t => { |  | ||||||
|   t.context.log = stub(console, 'log'); |  | ||||||
|   t.context.error = stub(console, 'error'); |  | ||||||
| }); |  | ||||||
| 
 |  | ||||||
| test.afterEach.always(t => { |  | ||||||
|   t.context.log.restore(); |  | ||||||
|   t.context.error.restore(); |  | ||||||
| }); |  | ||||||
| 
 |  | ||||||
| test.serial('Basic log', t => { |  | ||||||
|   logger.log('test log'); |  | ||||||
|   logger.error('test error'); |  | ||||||
| 
 |  | ||||||
|   t.regex(t.context.log.args[0][0], /.*test log/); |  | ||||||
|   t.regex(t.context.error.args[0][0], /.*test error/); |  | ||||||
| }); |  | ||||||
| 
 |  | ||||||
| test.serial('Log object', t => { |  | ||||||
|   const obj = {a: 1, b: '2'}; |  | ||||||
|   logger.log(obj); |  | ||||||
|   logger.error(obj); |  | ||||||
| 
 |  | ||||||
|   t.is(t.context.log.args[0][1], obj); |  | ||||||
|   t.is(t.context.error.args[0][1], obj); |  | ||||||
| }); |  | ||||||
| 
 |  | ||||||
| test.serial('Log with string formatting', t => { |  | ||||||
|   logger.log('test log %s', 'log value'); |  | ||||||
|   logger.error('test error %s', 'error value'); |  | ||||||
| 
 |  | ||||||
|   t.regex(t.context.log.args[0][0], /.*test log/); |  | ||||||
|   t.regex(t.context.error.args[0][0], /.*test error/); |  | ||||||
|   t.is(t.context.log.args[0][1], 'log value'); |  | ||||||
|   t.is(t.context.error.args[0][1], 'error value'); |  | ||||||
| }); |  | ||||||
| 
 |  | ||||||
| test.serial('Log with error stacktrace and properties', t => { |  | ||||||
|   const error = new Error('error message'); |  | ||||||
|   logger.error(error); |  | ||||||
|   const otherError = new Error('other error message'); |  | ||||||
|   logger.error('test error %O', otherError); |  | ||||||
| 
 |  | ||||||
|   t.is(t.context.error.args[0][1], error); |  | ||||||
|   t.regex(t.context.error.args[1][0], /.*test error/); |  | ||||||
|   t.is(t.context.error.args[1][1], otherError); |  | ||||||
| }); |  | ||||||
| @ -8,7 +8,15 @@ const cwd = process.cwd(); | |||||||
| test.beforeEach(t => { | test.beforeEach(t => { | ||||||
|   // Stub the logger functions
 |   // Stub the logger functions
 | ||||||
|   t.context.log = stub(); |   t.context.log = stub(); | ||||||
|   t.context.logger = {log: t.context.log}; |   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 => { | test('Normalize and load plugin from string', t => { | ||||||
| @ -21,7 +29,7 @@ test('Normalize and load plugin from string', t => { | |||||||
| 
 | 
 | ||||||
|   t.is(plugin.pluginName, './test/fixtures/plugin-noop'); |   t.is(plugin.pluginName, './test/fixtures/plugin-noop'); | ||||||
|   t.is(typeof plugin, 'function'); |   t.is(typeof plugin, 'function'); | ||||||
|   t.deepEqual(t.context.log.args[0], ['Load plugin "%s" from %s', 'verifyConditions', './test/fixtures/plugin-noop']); |   t.deepEqual(t.context.success.args[0], ['Loaded plugin "verifyConditions" from "./test/fixtures/plugin-noop"']); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| test('Normalize and load plugin from object', t => { | test('Normalize and load plugin from object', t => { | ||||||
| @ -34,7 +42,7 @@ test('Normalize and load plugin from object', t => { | |||||||
| 
 | 
 | ||||||
|   t.is(plugin.pluginName, './test/fixtures/plugin-noop'); |   t.is(plugin.pluginName, './test/fixtures/plugin-noop'); | ||||||
|   t.is(typeof plugin, 'function'); |   t.is(typeof plugin, 'function'); | ||||||
|   t.deepEqual(t.context.log.args[0], ['Load plugin "%s" from %s', 'publish', './test/fixtures/plugin-noop']); |   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 => { | test('Normalize and load plugin from a base file path', t => { | ||||||
| @ -44,11 +52,8 @@ test('Normalize and load plugin from a base file path', t => { | |||||||
| 
 | 
 | ||||||
|   t.is(plugin.pluginName, './plugin-noop'); |   t.is(plugin.pluginName, './plugin-noop'); | ||||||
|   t.is(typeof plugin, 'function'); |   t.is(typeof plugin, 'function'); | ||||||
|   t.deepEqual(t.context.log.args[0], [ |   t.deepEqual(t.context.success.args[0], [ | ||||||
|     'Load plugin "%s" from %s in shareable config %s', |     'Loaded plugin "verifyConditions" from "./plugin-noop" in shareable config "./test/fixtures"', | ||||||
|     'verifyConditions', |  | ||||||
|     './plugin-noop', |  | ||||||
|     './test/fixtures', |  | ||||||
|   ]); |   ]); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| @ -90,12 +95,17 @@ test('Normalize and load plugin that retuns multiple functions', t => { | |||||||
|   ); |   ); | ||||||
| 
 | 
 | ||||||
|   t.is(typeof plugin, 'function'); |   t.is(typeof plugin, 'function'); | ||||||
|   t.deepEqual(t.context.log.args[0], ['Load plugin "%s" from %s', 'verifyConditions', './test/fixtures/multi-plugin']); |   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 => { | test('Wrap "analyzeCommits" plugin in a function that validate the output of the plugin', async t => { | ||||||
|   const analyzeCommits = stub().resolves(2); |   const analyzeCommits = stub().resolves(2); | ||||||
|   const plugin = normalize({cwd, options: {}, logger: t.context.logger}, 'analyzeCommits', analyzeCommits, {}); |   const plugin = normalize( | ||||||
|  |     {cwd, options: {}, stderr: t.context.stderr, logger: t.context.logger}, | ||||||
|  |     'analyzeCommits', | ||||||
|  |     analyzeCommits, | ||||||
|  |     {} | ||||||
|  |   ); | ||||||
| 
 | 
 | ||||||
|   const error = await t.throws(plugin()); |   const error = await t.throws(plugin()); | ||||||
| 
 | 
 | ||||||
| @ -108,7 +118,12 @@ test('Wrap "analyzeCommits" plugin in a function that validate the output of the | |||||||
| 
 | 
 | ||||||
| test('Wrap "generateNotes" plugin in a function that validate the output of the plugin', async t => { | test('Wrap "generateNotes" plugin in a function that validate the output of the plugin', async t => { | ||||||
|   const generateNotes = stub().resolves(2); |   const generateNotes = stub().resolves(2); | ||||||
|   const plugin = normalize({cwd, options: {}, logger: t.context.logger}, 'generateNotes', generateNotes, {}); |   const plugin = normalize( | ||||||
|  |     {cwd, options: {}, stderr: t.context.stderr, logger: t.context.logger}, | ||||||
|  |     'generateNotes', | ||||||
|  |     generateNotes, | ||||||
|  |     {} | ||||||
|  |   ); | ||||||
| 
 | 
 | ||||||
|   const error = await t.throws(plugin()); |   const error = await t.throws(plugin()); | ||||||
| 
 | 
 | ||||||
| @ -120,11 +135,15 @@ test('Wrap "generateNotes" plugin in a function that validate the output of the | |||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| test('Wrap "publish" plugin in a function that validate the output of the plugin', async t => { | test('Wrap "publish" plugin in a function that validate the output of the plugin', async t => { | ||||||
|   const plugin = normalize({cwd, options: {}, logger: t.context.logger}, 'publish', './plugin-identity', { |   const publish = stub().resolves(2); | ||||||
|     './plugin-identity': './test/fixtures', |   const plugin = normalize( | ||||||
|   }); |     {cwd, options: {}, stderr: t.context.stderr, logger: t.context.logger}, | ||||||
|  |     'publish', | ||||||
|  |     publish, | ||||||
|  |     {} | ||||||
|  |   ); | ||||||
| 
 | 
 | ||||||
|   const error = await t.throws(plugin(2)); |   const error = await t.throws(plugin()); | ||||||
| 
 | 
 | ||||||
|   t.is(error.code, 'EPUBLISHOUTPUT'); |   t.is(error.code, 'EPUBLISHOUTPUT'); | ||||||
|   t.is(error.name, 'SemanticReleaseError'); |   t.is(error.name, 'SemanticReleaseError'); | ||||||
| @ -138,9 +157,11 @@ test('Plugin is called with "pluginConfig" (omitting "path", adding global confi | |||||||
|   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'); |   await plugin({param: 'param'}); | ||||||
| 
 | 
 | ||||||
|   t.true(pluginFunction.calledWith({conf: 'confValue', global: 'globalValue'}, 'param')); |   t.true( | ||||||
|  |     pluginFunction.calledWith({conf: 'confValue', global: 'globalValue'}, {param: 'param', logger: t.context.logger}) | ||||||
|  |   ); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| test('Prevent plugins to modify "pluginConfig"', async t => { | test('Prevent plugins to modify "pluginConfig"', async t => { | ||||||
|  | |||||||
| @ -11,7 +11,8 @@ const cwd = process.cwd(); | |||||||
| test.beforeEach(t => { | test.beforeEach(t => { | ||||||
|   // Stub the logger functions
 |   // Stub the logger functions
 | ||||||
|   t.context.log = stub(); |   t.context.log = stub(); | ||||||
|   t.context.logger = {log: t.context.log}; |   t.context.success = stub(); | ||||||
|  |   t.context.logger = {log: t.context.log, success: t.context.success, scope: () => t.context.logger}; | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| test('Export default plugins', t => { | test('Export default plugins', t => { | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user