refactor: Use ES6, Test with AVA
- Use async/await instead of callbacks - Use execa to run command line - Use AVA for tests - Add several assertions in the unit tests - Add documentation (comments) in the tests - Run tests with a real git repo instead of mocking child_process and add test helpers to create repos, commits and checkout - Simplify test directory structure - Simplify code readability (mostly with async/await) - Use eslint for for linting, prettier for formatting
This commit is contained in:
		
							parent
							
								
									7fe0890350
								
							
						
					
					
						commit
						abf92ad03d
					
				| @ -3,11 +3,11 @@ | |||||||
| // Bad news: We have to write plain ES5 in this file
 | // Bad news: We have to write plain ES5 in this file
 | ||||||
| // Good news: It's the only file of the entire project
 | // Good news: It's the only file of the entire project
 | ||||||
| 
 | 
 | ||||||
| var semver = require('semver') | var semver = require('semver'); | ||||||
| 
 | 
 | ||||||
| if (semver.lt(process.version, '8.0.0')) { | if (semver.lt(process.version, '8.0.0')) { | ||||||
|   console.error( |   console.error( | ||||||
| `semantic-release: node version >= 8 is required. Found ${process.version}.
 |     `semantic-release: node version >= 8 is required. Found ${process.version}.
 | ||||||
| 
 | 
 | ||||||
| If there is another job running on node version >= 8, it will be picked as | If there is another job running on node version >= 8, it will be picked as | ||||||
| the build leader and you can safely ignore this message. | the build leader and you can safely ignore this message. | ||||||
| @ -17,9 +17,10 @@ compatibility with minimal overhead: | |||||||
| 
 | 
 | ||||||
| $ npx -p node@8 npm run semantic-release | $ npx -p node@8 npm run semantic-release | ||||||
| 
 | 
 | ||||||
| npx is bundled with npm >= 5.4, or available via npm. More info: npm.im/npx`)
 | npx is bundled with npm >= 5.4, or available via npm. More info: npm.im/npx` | ||||||
|   process.exit(1) |   ); | ||||||
|  |   process.exit(1); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // node 8+ from this point on
 | // node 8+ from this point on
 | ||||||
| require('../src') | require('../src')(); | ||||||
|  | |||||||
							
								
								
									
										70
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										70
									
								
								package.json
									
									
									
									
									
								
							| @ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|   "name": "semantic-release", |   "name": "semantic-release", | ||||||
|   "description": "automated semver compliant package publishing", |   "description": "Automated semver compliant package publishing", | ||||||
|   "version": "0.0.0-placeholder", |   "version": "0.0.0-placeholder", | ||||||
|   "author": "Stephan Bönnemann <stephan@boennemann.me> (http://boennemann.me)", |   "author": "Stephan Bönnemann <stephan@boennemann.me> (http://boennemann.me)", | ||||||
|   "bin": { |   "bin": { | ||||||
| @ -20,38 +20,60 @@ | |||||||
|     "@semantic-release/error": "^2.0.0", |     "@semantic-release/error": "^2.0.0", | ||||||
|     "@semantic-release/last-release-npm": "^2.0.0", |     "@semantic-release/last-release-npm": "^2.0.0", | ||||||
|     "@semantic-release/release-notes-generator": "^4.0.0", |     "@semantic-release/release-notes-generator": "^4.0.0", | ||||||
|  |     "execa": "^0.8.0", | ||||||
|  |     "fs-extra": "^4.0.2", | ||||||
|     "git-head": "^1.2.1", |     "git-head": "^1.2.1", | ||||||
|     "github": "^8.0.0", |     "github": "^11.0.0", | ||||||
|     "lodash": "^4.0.0", |     "lodash": "^4.0.0", | ||||||
|     "nerf-dart": "^1.0.0", |     "nerf-dart": "^1.0.0", | ||||||
|     "nopt": "^4.0.0", |     "nopt": "^4.0.0", | ||||||
|     "normalize-package-data": "^2.3.4", |     "normalize-package-data": "^2.3.4", | ||||||
|     "npmconf": "^2.1.2", |     "npmconf": "^2.1.2", | ||||||
|     "npmlog": "^4.0.0", |     "npmlog": "^4.0.0", | ||||||
|  |     "p-series": "^1.0.0", | ||||||
|     "parse-github-repo-url": "^1.3.0", |     "parse-github-repo-url": "^1.3.0", | ||||||
|     "require-relative": "^0.8.7", |     "require-relative": "^0.8.7", | ||||||
|     "run-auto": "^2.0.0", |     "semver": "^5.4.1" | ||||||
|     "run-series": "^1.1.3", |  | ||||||
|     "semver": "^5.2.0" |  | ||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "coveralls": "^3.0.0", |     "coveralls": "^3.0.0", | ||||||
|  |     "ava": "^0.22.0", | ||||||
|  |     "commitizen": "^2.9.6", | ||||||
|     "cz-conventional-changelog": "^2.0.0", |     "cz-conventional-changelog": "^2.0.0", | ||||||
|     "mkdirp": "^0.5.1", |     "eslint": "^4.7.0", | ||||||
|     "mock-spawn": "^0.2.6", |     "eslint-config-prettier": "^2.5.0", | ||||||
|     "nixt": "^0.5.0", |     "eslint-config-standard": "^10.2.1", | ||||||
|  |     "eslint-plugin-import": "^2.7.0", | ||||||
|  |     "eslint-plugin-node": "^5.2.0", | ||||||
|  |     "eslint-plugin-prettier": "^2.3.0", | ||||||
|  |     "eslint-plugin-promise": "^3.5.0", | ||||||
|  |     "eslint-plugin-standard": "^3.0.1", | ||||||
|     "nock": "^9.0.2", |     "nock": "^9.0.2", | ||||||
|     "npm-registry-couchapp": "^2.6.12", |     "npm-registry-couchapp": "^2.6.12", | ||||||
|     "nyc": "^10.0.0", |     "nyc": "^11.2.1", | ||||||
|  |     "p-map-series": "^1.0.0", | ||||||
|  |     "prettier": "^1.7.0", | ||||||
|     "proxyquire": "^1.7.3", |     "proxyquire": "^1.7.3", | ||||||
|     "rimraf": "^2.5.0", |     "rimraf": "^2.5.0", | ||||||
|     "standard": "^9.0.0", |     "sinon": "^4.0.0", | ||||||
|     "tap": "^10.0.1" |     "tempy": "^0.2.1" | ||||||
|   }, |   }, | ||||||
|   "engines": { |   "engines": { | ||||||
|     "node": ">=4", |     "node": ">=4", | ||||||
|     "npm": ">=2" |     "npm": ">=2" | ||||||
|   }, |   }, | ||||||
|  |   "eslintConfig": { | ||||||
|  |     "extends": [ | ||||||
|  |       "standard", | ||||||
|  |       "prettier" | ||||||
|  |     ], | ||||||
|  |     "plugins": [ | ||||||
|  |       "prettier" | ||||||
|  |     ], | ||||||
|  |     "rules": { | ||||||
|  |       "prettier/prettier": 2 | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|   "files": [ |   "files": [ | ||||||
|     "bin", |     "bin", | ||||||
|     "src" |     "src" | ||||||
| @ -70,6 +92,23 @@ | |||||||
|   ], |   ], | ||||||
|   "license": "MIT", |   "license": "MIT", | ||||||
|   "main": "bin/semantic-release.js", |   "main": "bin/semantic-release.js", | ||||||
|  |   "nyc": { | ||||||
|  |     "include": [ | ||||||
|  |       "src/**/*.js" | ||||||
|  |     ], | ||||||
|  |     "reporter": [ | ||||||
|  |       "json", | ||||||
|  |       "text", | ||||||
|  |       "html" | ||||||
|  |     ], | ||||||
|  |     "all": true | ||||||
|  |   }, | ||||||
|  |   "prettier": { | ||||||
|  |     "printWidth": 120, | ||||||
|  |     "singleQuote": true, | ||||||
|  |     "bracketSpacing": false, | ||||||
|  |     "trailingComma": "es5" | ||||||
|  |   }, | ||||||
|   "publishConfig": { |   "publishConfig": { | ||||||
|     "tag": "next" |     "tag": "next" | ||||||
|   }, |   }, | ||||||
| @ -83,10 +122,11 @@ | |||||||
|   "scripts": { |   "scripts": { | ||||||
|     "coverage": "nyc report", |     "coverage": "nyc report", | ||||||
|     "coverage:upload": "npm run coverage -s -- --reporter=text-lcov | coveralls", |     "coverage:upload": "npm run coverage -s -- --reporter=text-lcov | coveralls", | ||||||
|     "pretest": "standard", |     "clean": "rimraf coverage && rimraf .nyc_output", | ||||||
|  |     "cm": "git-cz", | ||||||
|  |     "lint": "eslint .", | ||||||
|  |     "pretest": "npm run clean && npm run lint", | ||||||
|     "semantic-release": "./bin/semantic-release.js pre && npm publish && ./bin/semantic-release.js post", |     "semantic-release": "./bin/semantic-release.js pre && npm publish && ./bin/semantic-release.js post", | ||||||
|     "test": "npm run test:unit && npm run test:integration", |     "test": "nyc ava -v" | ||||||
|     "test:integration": "tap --no-cov test/scenarios/*.js", |  | ||||||
|     "test:unit": "nyc tap --no-cov test/specs/*.js" |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										258
									
								
								src/index.js
									
									
									
									
									
								
							
							
						
						
									
										258
									
								
								src/index.js
									
									
									
									
									
								
							| @ -1,163 +1,153 @@ | |||||||
| var fs = require('fs') | const path = require('path'); | ||||||
| var path = require('path') | const {promisify} = require('util'); | ||||||
| var url = require('url') | const url = require('url'); | ||||||
|  | const {readJson, writeJson} = require('fs-extra'); | ||||||
|  | const {cloneDeep, defaults, mapKeys, camelCase, assign} = require('lodash'); | ||||||
|  | const log = require('npmlog'); | ||||||
|  | const nopt = require('nopt'); | ||||||
|  | const npmconf = require('npmconf'); | ||||||
|  | const normalizeData = require('normalize-package-data'); | ||||||
| 
 | 
 | ||||||
| var _ = require('lodash') | module.exports = async () => { | ||||||
| var log = require('npmlog') |   log.heading = 'semantic-release'; | ||||||
| var nopt = require('nopt') |   const env = process.env; | ||||||
| var npmconf = require('npmconf') |   const pkg = await readJson('./package.json'); | ||||||
| var normalizeData = require('normalize-package-data') |   const originalPkg = cloneDeep(pkg); | ||||||
| 
 |   normalizeData(pkg); | ||||||
| log.heading = 'semantic-release' |   const knownOptions = { | ||||||
| var env = process.env |     branch: String, | ||||||
| var pkg = JSON.parse(fs.readFileSync('./package.json')) |     debug: Boolean, | ||||||
| var originalPkg = _.cloneDeep(pkg) |     'github-token': String, | ||||||
| normalizeData(pkg) |     'github-url': String, | ||||||
| var knownOptions = { |     'analyze-commits': [path, String], | ||||||
|   branch: String, |     'generate-notes': [path, String], | ||||||
|   debug: Boolean, |     'verify-conditions': [path, String], | ||||||
|   'github-token': String, |     'verify-release': [path, String], | ||||||
|   'github-url': String, |   }; | ||||||
|   'analyze-commits': [path, String], |   const options = defaults( | ||||||
|   'generate-notes': [path, String], |     mapKeys(nopt(knownOptions), (value, key) => { | ||||||
|   'verify-conditions': [path, String], |       return camelCase(key); | ||||||
|   'verify-release': [path, String] |     }), | ||||||
| } |     pkg.release, | ||||||
| var options = _.defaults( |     { | ||||||
|   _.mapKeys(nopt(knownOptions), function (value, key) { |       branch: 'master', | ||||||
|     return _.camelCase(key) |       fallbackTags: {next: 'latest'}, | ||||||
|   }), |       debug: !env.CI, | ||||||
|   pkg.release, |       githubToken: env.GH_TOKEN || env.GITHUB_TOKEN, | ||||||
|   { |       githubUrl: env.GH_URL, | ||||||
|     branch: 'master', |     } | ||||||
|     fallbackTags: { |   ); | ||||||
|       next: 'latest' |   const plugins = require('../src/lib/plugins')(options); | ||||||
|     }, |   let conf; | ||||||
|     debug: !env.CI, |   try { | ||||||
|     githubToken: env.GH_TOKEN || env.GITHUB_TOKEN, |     conf = await promisify(npmconf.load)({}); | ||||||
|     githubUrl: env.GH_URL |   } catch (err) { | ||||||
|   } |     log.error('init', 'Failed to load npm config.', err); | ||||||
| ) |     process.exit(1); | ||||||
| var plugins = require('../src/lib/plugins')(options) |  | ||||||
| 
 |  | ||||||
| npmconf.load({}, function (err, conf) { |  | ||||||
|   if (err) { |  | ||||||
|     log.error('init', 'Failed to load npm config.', err) |  | ||||||
|     process.exit(1) |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   var npm = { |   const npm = { | ||||||
|     auth: { |     auth: {token: env.NPM_TOKEN}, | ||||||
|       token: env.NPM_TOKEN |  | ||||||
|     }, |  | ||||||
|     cafile: conf.get('cafile'), |     cafile: conf.get('cafile'), | ||||||
|     loglevel: conf.get('loglevel'), |     loglevel: conf.get('loglevel'), | ||||||
|     registry: require('../src/lib/get-registry')(pkg, conf), |     registry: require('../src/lib/get-registry')(pkg, conf), | ||||||
|     tag: (pkg.publishConfig || {}).tag || conf.get('tag') || 'latest' |     tag: (pkg.publishConfig || {}).tag || conf.get('tag') || 'latest', | ||||||
|   } |   }; | ||||||
| 
 | 
 | ||||||
|   // normalize trailing slash
 |   // normalize trailing slash
 | ||||||
|   npm.registry = url.format(url.parse(npm.registry)) |   npm.registry = url.format(url.parse(npm.registry)); | ||||||
|  |   log.level = npm.loglevel; | ||||||
| 
 | 
 | ||||||
|   log.level = npm.loglevel |   const config = {env: env, pkg: pkg, options: options, plugins: plugins, npm: npm}; | ||||||
|  |   const hide = {}; | ||||||
|  |   if (options.githubToken) hide.githubToken = '***'; | ||||||
| 
 | 
 | ||||||
|   var config = { |   log.verbose('init', 'options:', assign({}, options, hide)); | ||||||
|     env: env, |   log.verbose('init', 'Verifying config.'); | ||||||
|     pkg: pkg, |  | ||||||
|     options: options, |  | ||||||
|     plugins: plugins, |  | ||||||
|     npm: npm |  | ||||||
|   } |  | ||||||
| 
 | 
 | ||||||
|   var hide = {} |   const errors = require('../src/lib/verify')(config); | ||||||
|   if (options.githubToken) hide.githubToken = '***' |   errors.forEach(err => { | ||||||
| 
 |     log.error('init', err.message + ' ' + err.code); | ||||||
|   log.verbose('init', 'options:', _.assign({}, options, hide)) |   }); | ||||||
|   log.verbose('init', 'Verifying config.') |   if (errors.length) process.exit(1); | ||||||
| 
 |  | ||||||
|   var errors = require('../src/lib/verify')(config) |  | ||||||
|   errors.forEach(function (err) { |  | ||||||
|     log.error('init', err.message + ' ' + err.code) |  | ||||||
|   }) |  | ||||||
|   if (errors.length) process.exit(1) |  | ||||||
| 
 | 
 | ||||||
|   if (options.argv.remain[0] === 'pre') { |   if (options.argv.remain[0] === 'pre') { | ||||||
|     log.verbose('pre', 'Running pre-script.') |     log.verbose('pre', 'Running pre-script.'); | ||||||
|     log.verbose('pre', 'Veriying conditions.') |     log.verbose('pre', 'Veriying conditions.'); | ||||||
|  |     try { | ||||||
|  |       await promisify(plugins.verifyConditions)(config); | ||||||
|  |     } catch (err) { | ||||||
|  |       log[options.debug ? 'warn' : 'error']('pre', err.message); | ||||||
|  |       if (!options.debug) process.exit(1); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     plugins.verifyConditions(config, function (err) { |     const nerfDart = require('nerf-dart')(npm.registry); | ||||||
|       if (err) { |     let wroteNpmRc = false; | ||||||
|         log[options.debug ? 'warn' : 'error']('pre', err.message) |  | ||||||
|         if (!options.debug) process.exit(1) |  | ||||||
|       } |  | ||||||
| 
 | 
 | ||||||
|       var nerfDart = require('nerf-dart')(npm.registry) |     if (env.NPM_OLD_TOKEN && env.NPM_EMAIL) { | ||||||
|       var wroteNpmRc = false |       // Using the old auth token format is not considered part of the public API
 | ||||||
|  |       // This might go away anytime (i.e. once we have a better testing strategy)
 | ||||||
|  |       conf.set('_auth', '${NPM_OLD_TOKEN}', 'project'); // eslint-disable-line no-template-curly-in-string
 | ||||||
|  |       conf.set('email', '${NPM_EMAIL}', 'project'); // eslint-disable-line no-template-curly-in-string
 | ||||||
|  |       wroteNpmRc = true; | ||||||
|  |     } else if (env.NPM_TOKEN) { | ||||||
|  |       conf.set(nerfDart + ':_authToken', '${NPM_TOKEN}', 'project'); // eslint-disable-line no-template-curly-in-string
 | ||||||
|  |       wroteNpmRc = true; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|       if (env.NPM_OLD_TOKEN && env.NPM_EMAIL) { |     try { | ||||||
|         // Using the old auth token format is not considered part of the public API
 |       await promisify(conf.save.bind(conf))('project'); | ||||||
|         // This might go away anytime (i.e. once we have a better testing strategy)
 |     } catch (err) { | ||||||
|         conf.set('_auth', '${NPM_OLD_TOKEN}', 'project') // eslint-disable-line no-template-curly-in-string
 |       return log.error('pre', 'Failed to save npm config.', err); | ||||||
|         conf.set('email', '${NPM_EMAIL}', 'project') // eslint-disable-line no-template-curly-in-string
 |     } | ||||||
|         wroteNpmRc = true |  | ||||||
|       } else if (env.NPM_TOKEN) { |  | ||||||
|         conf.set(nerfDart + ':_authToken', '${NPM_TOKEN}', 'project') // eslint-disable-line no-template-curly-in-string
 |  | ||||||
|         wroteNpmRc = true |  | ||||||
|       } |  | ||||||
| 
 | 
 | ||||||
|       conf.save('project', function (err) { |     if (wroteNpmRc) log.verbose('pre', 'Wrote authToken to .npmrc.'); | ||||||
|         if (err) return log.error('pre', 'Failed to save npm config.', err) |  | ||||||
| 
 | 
 | ||||||
|         if (wroteNpmRc) log.verbose('pre', 'Wrote authToken to .npmrc.') |     let release; | ||||||
|  |     try { | ||||||
|  |       release = await require('../src/pre')(config); | ||||||
|  |     } catch (err) { | ||||||
|  |       log.error('pre', 'Failed to determine new version.'); | ||||||
| 
 | 
 | ||||||
|         require('../src/pre')(config, function (err, release) { |       const args = ['pre', (err.code ? err.code + ' ' : '') + err.message]; | ||||||
|           if (err) { |       if (err.stack) args.push(err.stack); | ||||||
|             log.error('pre', 'Failed to determine new version.') |       log.error.apply(log, args); | ||||||
|  |       process.exit(1); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|             var args = ['pre', (err.code ? err.code + ' ' : '') + err.message] |     const message = 'Determined version ' + release.version + ' as "' + npm.tag + '".'; | ||||||
|             if (err.stack) args.push(err.stack) |  | ||||||
|             log.error.apply(log, args) |  | ||||||
|             process.exit(1) |  | ||||||
|           } |  | ||||||
| 
 | 
 | ||||||
|           var message = 'Determined version ' + release.version + ' as "' + npm.tag + '".' |     log.verbose('pre', message); | ||||||
| 
 | 
 | ||||||
|           log.verbose('pre', message) |     if (options.debug) { | ||||||
|  |       log.error('pre', message + ' Not publishing in debug mode.', release); | ||||||
|  |       process.exit(1); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|           if (options.debug) { |     try { | ||||||
|             log.error('pre', message + ' Not publishing in debug mode.', release) |       const shrinkwrap = await readJson('./npm-shrinkwrap.json'); | ||||||
|             process.exit(1) |       shrinkwrap.version = release.version; | ||||||
|           } |       await writeJson('./npm-shrinkwrap.json', shrinkwrap); | ||||||
|  |       log.verbose('pre', 'Wrote version ' + release.version + 'to npm-shrinkwrap.json.'); | ||||||
|  |     } catch (e) { | ||||||
|  |       log.silly('pre', "Couldn't find npm-shrinkwrap.json."); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|           try { |     await writeJson('./package.json', assign(originalPkg, {version: release.version})); | ||||||
|             var shrinkwrap = JSON.parse(fs.readFileSync('./npm-shrinkwrap.json')) |  | ||||||
|             shrinkwrap.version = release.version |  | ||||||
|             fs.writeFileSync('./npm-shrinkwrap.json', JSON.stringify(shrinkwrap, null, 2)) |  | ||||||
|             log.verbose('pre', 'Wrote version ' + release.version + 'to npm-shrinkwrap.json.') |  | ||||||
|           } catch (e) { |  | ||||||
|             log.silly('pre', 'Couldn\'t find npm-shrinkwrap.json.') |  | ||||||
|           } |  | ||||||
| 
 | 
 | ||||||
|           fs.writeFileSync('./package.json', JSON.stringify(_.assign(originalPkg, { |     log.verbose('pre', 'Wrote version ' + release.version + ' to package.json.'); | ||||||
|             version: release.version |  | ||||||
|           }), null, 2)) |  | ||||||
| 
 |  | ||||||
|           log.verbose('pre', 'Wrote version ' + release.version + ' to package.json.') |  | ||||||
|         }) |  | ||||||
|       }) |  | ||||||
|     }) |  | ||||||
|   } else if (options.argv.remain[0] === 'post') { |   } else if (options.argv.remain[0] === 'post') { | ||||||
|     log.verbose('post', 'Running post-script.') |     log.verbose('post', 'Running post-script.'); | ||||||
| 
 | 
 | ||||||
|     require('../src/post')(config, function (err, published, release) { |     let published, release; | ||||||
|       if (err) { |     try { | ||||||
|         log.error('post', 'Failed to publish release notes.', err) |       ({published, release} = await require('../src/post')(config)); | ||||||
|         process.exit(1) |       log.verbose('post', (published ? 'Published' : 'Generated') + ' release notes.', release); | ||||||
|       } |     } catch (err) { | ||||||
| 
 |       log.error('post', 'Failed to publish release notes.', err); | ||||||
|       log.verbose('post', (published ? 'Published' : 'Generated') + ' release notes.', release) |       process.exit(1); | ||||||
|     }) |     } | ||||||
|   } else { |   } else { | ||||||
|     log.error('post', 'Command "' + options.argv.remain[0] + '" not recognized. Use either "pre" or "post"') |     log.error('post', 'Command "' + options.argv.remain[0] + '" not recognized. Use either "pre" or "post"'); | ||||||
|   } |   } | ||||||
| }) | }; | ||||||
|  | |||||||
| @ -1,79 +0,0 @@ | |||||||
| var childProcess = require('child_process') |  | ||||||
| 
 |  | ||||||
| var log = require('npmlog') |  | ||||||
| 
 |  | ||||||
| var SemanticReleaseError = require('@semantic-release/error') |  | ||||||
| 
 |  | ||||||
| module.exports = function (config, cb) { |  | ||||||
|   var lastRelease = config.lastRelease |  | ||||||
|   var options = config.options |  | ||||||
|   var branch = options.branch |  | ||||||
|   var from = lastRelease.gitHead |  | ||||||
|   var range = (from ? from + '..' : '') + 'HEAD' |  | ||||||
| 
 |  | ||||||
|   if (!from) return extract() |  | ||||||
| 
 |  | ||||||
|   childProcess.exec('git branch --no-color --contains ' + from, function (err, stdout) { |  | ||||||
|     var inHistory = false |  | ||||||
|     var branches |  | ||||||
| 
 |  | ||||||
|     if (!err && stdout) { |  | ||||||
|       branches = stdout.split('\n') |  | ||||||
|       .map(function (result) { |  | ||||||
|         if (branch === result.replace('*', '').trim()) { |  | ||||||
|           inHistory = true |  | ||||||
|           return null |  | ||||||
|         } |  | ||||||
|         return result.trim() |  | ||||||
|       }) |  | ||||||
|       .filter(function (branch) { |  | ||||||
|         return !!branch |  | ||||||
|       }) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if (!inHistory) { |  | ||||||
|       log.error('commits', |  | ||||||
|         'The commit the last release of this package was derived from is not in the direct history of the "' + branch + '" branch.\n' + |  | ||||||
|         'This means semantic-release can not extract the commits between now and then.\n' + |  | ||||||
|         'This is usually caused by force pushing, releasing from an unrelated branch, or using an already existing package name.\n' + |  | ||||||
|         'You can recover from this error by publishing manually or restoring the commit "' + from + '".' + (branches && branches.length |  | ||||||
|         ? '\nHere is a list of branches that still contain the commit in question: \n * ' + branches.join('\n * ') |  | ||||||
|         : '' |  | ||||||
|       )) |  | ||||||
|       return cb(new SemanticReleaseError('Commit not in history', 'ENOTINHISTORY')) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     extract() |  | ||||||
|   }) |  | ||||||
| 
 |  | ||||||
|   function extract () { |  | ||||||
|     var child = childProcess.spawn('git', ['log', '-E', '--format=%H==SPLIT==%B==END==', range]) |  | ||||||
|     var stdout = '' |  | ||||||
|     var err = '' |  | ||||||
| 
 |  | ||||||
|     child.stdout.on('data', function (data) { |  | ||||||
|       stdout += data |  | ||||||
|     }) |  | ||||||
| 
 |  | ||||||
|     child.stderr.on('data', function (data) { |  | ||||||
|       err += data |  | ||||||
|     }) |  | ||||||
| 
 |  | ||||||
|     child.on('close', function (code) { |  | ||||||
|       if (err || code) return cb(err) |  | ||||||
| 
 |  | ||||||
|       cb(null, String(stdout).split('==END==\n') |  | ||||||
|         .filter(function (raw) { |  | ||||||
|           return !!raw.trim() |  | ||||||
|         }) |  | ||||||
|         .map(function (raw) { |  | ||||||
|           var data = raw.split('==SPLIT==') |  | ||||||
|           return { |  | ||||||
|             hash: data[0], |  | ||||||
|             message: data[1] |  | ||||||
|           } |  | ||||||
|         }) |  | ||||||
|       ) |  | ||||||
|     }) |  | ||||||
|   } |  | ||||||
| } |  | ||||||
							
								
								
									
										56
									
								
								src/lib/get-commits.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								src/lib/get-commits.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,56 @@ | |||||||
|  | const execa = require('execa'); | ||||||
|  | const log = require('npmlog'); | ||||||
|  | const SemanticReleaseError = require('@semantic-release/error'); | ||||||
|  | 
 | ||||||
|  | module.exports = async ({lastRelease, options}) => { | ||||||
|  |   let stdout; | ||||||
|  |   if (lastRelease.gitHead) { | ||||||
|  |     try { | ||||||
|  |       ({stdout} = await execa('git', ['branch', '--no-color', '--contains', lastRelease.gitHead])); | ||||||
|  |     } catch (err) { | ||||||
|  |       throw notInHistoryError(lastRelease.gitHead, options.branch); | ||||||
|  |     } | ||||||
|  |     const branches = stdout | ||||||
|  |       .split('\n') | ||||||
|  |       .map(branch => branch.replace('*', '').trim()) | ||||||
|  |       .filter(branch => !!branch); | ||||||
|  | 
 | ||||||
|  |     if (!branches.includes(options.branch)) { | ||||||
|  |       throw notInHistoryError(lastRelease.gitHead, options.branch, branches); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   try { | ||||||
|  |     ({stdout} = await execa('git', [ | ||||||
|  |       'log', | ||||||
|  |       '--format=%H==SPLIT==%B==END==', | ||||||
|  |       `${lastRelease.gitHead ? lastRelease.gitHead + '..' : ''}HEAD`, | ||||||
|  |     ])); | ||||||
|  |   } catch (err) { | ||||||
|  |     return []; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return String(stdout) | ||||||
|  |     .split('==END==') | ||||||
|  |     .filter(raw => !!raw.trim()) | ||||||
|  |     .map(raw => { | ||||||
|  |       const [hash, message] = raw.trim().split('==SPLIT=='); | ||||||
|  |       return {hash, message}; | ||||||
|  |     }); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | function notInHistoryError(gitHead, branch, branches) { | ||||||
|  |   log.error( | ||||||
|  |     'commits', | ||||||
|  |     ` | ||||||
|  | The commit the last release of this package was derived from is not in the direct history of the "${branch}" branch. | ||||||
|  | This means semantic-release can not extract the commits between now and then. | ||||||
|  | This is usually caused by force pushing, releasing from an unrelated branch, or using an already existing package name. | ||||||
|  | You can recover from this error by publishing manually or restoring the commit "${gitHead}". | ||||||
|  | ${branches && branches.length | ||||||
|  |       ? `\nHere is a list of branches that still contain the commit in question: \n * ${branches.join('\n * ')}` | ||||||
|  |       : ''} | ||||||
|  | ` | ||||||
|  |   ); | ||||||
|  |   return new SemanticReleaseError('Commit not in history', 'ENOTINHISTORY'); | ||||||
|  | } | ||||||
| @ -1,12 +1,11 @@ | |||||||
| module.exports = function (pkg, conf) { | module.exports = ({publishConfig, name}, conf) => { | ||||||
|   if (pkg.publishConfig && pkg.publishConfig.registry) return pkg.publishConfig.registry |   if (publishConfig && publishConfig.registry) { | ||||||
|  |     return publishConfig.registry; | ||||||
|  |   } | ||||||
| 
 | 
 | ||||||
|   if (pkg.name[0] !== '@') return conf.get('registry') || 'https://registry.npmjs.org/' |   if (name[0] !== '@') { | ||||||
|  |     return conf.get('registry') || 'https://registry.npmjs.org/'; | ||||||
|  |   } | ||||||
| 
 | 
 | ||||||
|   var scope = pkg.name.split('/')[0] |   return conf.get(`${name.split('/')[0]}/registry`) || conf.get('registry') || 'https://registry.npmjs.org/'; | ||||||
|   var scopedRegistry = conf.get(scope + '/registry') | }; | ||||||
| 
 |  | ||||||
|   if (scopedRegistry) return scopedRegistry |  | ||||||
| 
 |  | ||||||
|   return conf.get('registry') || 'https://registry.npmjs.org/' |  | ||||||
| } |  | ||||||
|  | |||||||
							
								
								
									
										14
									
								
								src/lib/get-release-type.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/lib/get-release-type.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,14 @@ | |||||||
|  | const {promisify} = require('util'); | ||||||
|  | const SemanticReleaseError = require('@semantic-release/error'); | ||||||
|  | 
 | ||||||
|  | module.exports = async config => { | ||||||
|  |   const {plugins, lastRelease} = config; | ||||||
|  |   const type = await promisify(plugins.analyzeCommits)(config); | ||||||
|  | 
 | ||||||
|  |   if (!type) { | ||||||
|  |     throw new SemanticReleaseError('There are no relevant changes, so no new version is released.', 'ENOCHANGE'); | ||||||
|  |   } | ||||||
|  |   if (!lastRelease.version) return 'initial'; | ||||||
|  | 
 | ||||||
|  |   return type; | ||||||
|  | }; | ||||||
| @ -1,4 +1,3 @@ | |||||||
| /* istanbul ignore next */ | module.exports = (config, options, cb) => { | ||||||
| module.exports = function (config, options, cb) { |   cb(null); | ||||||
|   cb(null) | }; | ||||||
| } |  | ||||||
|  | |||||||
| @ -1,42 +1,42 @@ | |||||||
| var relative = require('require-relative') | const {promisify} = require('util'); | ||||||
| var series = require('run-series') | const relative = require('require-relative'); | ||||||
|  | const pSeries = require('p-series'); | ||||||
| 
 | 
 | ||||||
| var exports = module.exports = function (options) { | module.exports = options => { | ||||||
|   var plugins = { |   const plugins = { | ||||||
|     analyzeCommits: exports.normalize(options.analyzeCommits, '@semantic-release/commit-analyzer'), |     analyzeCommits: normalize(options.analyzeCommits, '@semantic-release/commit-analyzer'), | ||||||
|     generateNotes: exports.normalize(options.generateNotes, '@semantic-release/release-notes-generator'), |     generateNotes: normalize(options.generateNotes, '@semantic-release/release-notes-generator'), | ||||||
|     getLastRelease: exports.normalize(options.getLastRelease, '@semantic-release/last-release-npm') |     getLastRelease: normalize(options.getLastRelease, '@semantic-release/last-release-npm'), | ||||||
|   } |   }; | ||||||
| 
 |   ['verifyConditions', 'verifyRelease'].forEach(plugin => { | ||||||
|   ;['verifyConditions', 'verifyRelease'].forEach(function (plugin) { |  | ||||||
|     if (!Array.isArray(options[plugin])) { |     if (!Array.isArray(options[plugin])) { | ||||||
|       plugins[plugin] = exports.normalize( |       plugins[plugin] = normalize( | ||||||
|         options[plugin], |         options[plugin], | ||||||
|         plugin === 'verifyConditions' |         plugin === 'verifyConditions' ? '@semantic-release/condition-travis' : './plugin-noop' | ||||||
|           ? '@semantic-release/condition-travis' |       ); | ||||||
|           : './plugin-noop' |       return; | ||||||
|       ) |  | ||||||
|       return |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     plugins[plugin] = function (pluginOptions, cb) { |     plugins[plugin] = async pluginOptions => { | ||||||
|       var tasks = options[plugin].map(function (step) { |       return pSeries( | ||||||
|         return exports.normalize(step, './plugin-noop').bind(null, pluginOptions) |         options[plugin].map(step => { | ||||||
|       }) |           return () => promisify(normalize(step, './plugin-noop'))(pluginOptions); | ||||||
|  |         }) | ||||||
|  |       ); | ||||||
|  |     }; | ||||||
|  |   }); | ||||||
| 
 | 
 | ||||||
|       series(tasks, cb) |   return plugins; | ||||||
|     } | }; | ||||||
|   }) |  | ||||||
| 
 | 
 | ||||||
|   return plugins | const normalize = (pluginConfig, fallback) => { | ||||||
| } |   if (typeof pluginConfig === 'string') return relative(pluginConfig).bind(null, {}); | ||||||
| 
 | 
 | ||||||
| exports.normalize = function (pluginConfig, fallback) { |   if (pluginConfig && typeof pluginConfig.path === 'string') { | ||||||
|   if (typeof pluginConfig === 'string') return relative(pluginConfig).bind(null, {}) |     return relative(pluginConfig.path).bind(null, pluginConfig); | ||||||
| 
 |  | ||||||
|   if (pluginConfig && (typeof pluginConfig.path === 'string')) { |  | ||||||
|     return relative(pluginConfig.path).bind(null, pluginConfig) |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   return require(fallback).bind(null, pluginConfig) |   return require(fallback).bind(null, pluginConfig); | ||||||
| } | }; | ||||||
|  | 
 | ||||||
|  | module.exports.normalize = normalize; | ||||||
|  | |||||||
| @ -1,21 +0,0 @@ | |||||||
| var SemanticReleaseError = require('@semantic-release/error') |  | ||||||
| 
 |  | ||||||
| module.exports = function (config, cb) { |  | ||||||
|   var plugins = config.plugins |  | ||||||
|   var lastRelease = config.lastRelease |  | ||||||
| 
 |  | ||||||
|   plugins.analyzeCommits(config, function (err, type) { |  | ||||||
|     if (err) return cb(err) |  | ||||||
| 
 |  | ||||||
|     if (!type) { |  | ||||||
|       return cb(new SemanticReleaseError( |  | ||||||
|         'There are no relevant changes, so no new version is released.', |  | ||||||
|         'ENOCHANGE' |  | ||||||
|       )) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if (!lastRelease.version) return cb(null, 'initial') |  | ||||||
| 
 |  | ||||||
|     cb(null, type) |  | ||||||
|   }) |  | ||||||
| } |  | ||||||
| @ -1,40 +1,25 @@ | |||||||
| var SemanticReleaseError = require('@semantic-release/error') | const SemanticReleaseError = require('@semantic-release/error'); | ||||||
| 
 | 
 | ||||||
| module.exports = function (config) { | module.exports = ({pkg, options, env}) => { | ||||||
|   var pkg = config.pkg |   const errors = []; | ||||||
|   var options = config.options |  | ||||||
|   var env = config.env |  | ||||||
|   var errors = [] |  | ||||||
| 
 | 
 | ||||||
|   if (!pkg.name) { |   if (!pkg.name) { | ||||||
|     errors.push(new SemanticReleaseError( |     errors.push(new SemanticReleaseError('No "name" found in package.json.', 'ENOPKGNAME')); | ||||||
|       'No "name" found in package.json.', |  | ||||||
|       'ENOPKGNAME' |  | ||||||
|     )) |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   if (!pkg.repository || !pkg.repository.url) { |   if (!pkg.repository || !pkg.repository.url) { | ||||||
|     errors.push(new SemanticReleaseError( |     errors.push(new SemanticReleaseError('No "repository" found in package.json.', 'ENOPKGREPO')); | ||||||
|       'No "repository" found in package.json.', |  | ||||||
|       'ENOPKGREPO' |  | ||||||
|     )) |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   if (options.debug) return errors |   if (!options.debug) { | ||||||
|  |     if (!options.githubToken) { | ||||||
|  |       errors.push(new SemanticReleaseError('No github token specified.', 'ENOGHTOKEN')); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|   if (!options.githubToken) { |     if (!(env.NPM_TOKEN || (env.NPM_OLD_TOKEN && env.NPM_EMAIL))) { | ||||||
|     errors.push(new SemanticReleaseError( |       errors.push(new SemanticReleaseError('No npm token specified.', 'ENONPMTOKEN')); | ||||||
|       'No github token specified.', |     } | ||||||
|       'ENOGHTOKEN' |  | ||||||
|     )) |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   if (!(env.NPM_TOKEN || (env.NPM_OLD_TOKEN && env.NPM_EMAIL))) { |   return errors; | ||||||
|     errors.push(new SemanticReleaseError( | }; | ||||||
|       'No npm token specified.', |  | ||||||
|       'ENONPMTOKEN' |  | ||||||
|     )) |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   return errors |  | ||||||
| } |  | ||||||
|  | |||||||
							
								
								
									
										95
									
								
								src/post.js
									
									
									
									
									
								
							
							
						
						
									
										95
									
								
								src/post.js
									
									
									
									
									
								
							| @ -1,71 +1,38 @@ | |||||||
| var url = require('url') | const {promisify} = require('util'); | ||||||
|  | const url = require('url'); | ||||||
|  | const gitHead = require('git-head'); | ||||||
|  | const GitHubApi = require('github'); | ||||||
|  | const parseSlug = require('parse-github-repo-url'); | ||||||
| 
 | 
 | ||||||
| var gitHead = require('git-head') | module.exports = async config => { | ||||||
| var GitHubApi = require('github') |   const {pkg, options: {branch, debug, githubUrl, githubToken, githubApiPathPrefix}, plugins} = config; | ||||||
| var parseSlug = require('parse-github-repo-url') |   const [owner, repo] = parseSlug(pkg.repository.url); | ||||||
|  |   const name = `v${pkg.version}`; | ||||||
|  |   const tag = {owner, repo, ref: `refs/tags/${name}`, sha: await promisify(gitHead)()}; | ||||||
|  |   const body = await promisify(plugins.generateNotes)(config); | ||||||
|  |   const release = {owner, repo, tag_name: name, name, target_commitish: branch, draft: !!debug, body}; | ||||||
| 
 | 
 | ||||||
| module.exports = function (config, cb) { |   if (debug && !githubToken) { | ||||||
|   var pkg = config.pkg |     return {published: false, release}; | ||||||
|   var options = config.options |   } | ||||||
|   var plugins = config.plugins |  | ||||||
|   var ghConfig = options.githubUrl ? url.parse(options.githubUrl) : {} |  | ||||||
| 
 | 
 | ||||||
|   var github = new GitHubApi({ |   const {port, protocol, hostname} = githubUrl ? url.parse(githubUrl) : {}; | ||||||
|     port: ghConfig.port, |   const github = new GitHubApi({ | ||||||
|     protocol: (ghConfig.protocol || '').split(':')[0] || null, |     port, | ||||||
|     host: ghConfig.hostname, |     protocol: (protocol || '').split(':')[0] || null, | ||||||
|     pathPrefix: options.githubApiPathPrefix || null |     host: hostname, | ||||||
|   }) |     pathPrefix: githubApiPathPrefix || null, | ||||||
|  |   }); | ||||||
| 
 | 
 | ||||||
|   plugins.generateNotes(config, function (err, log) { |   github.authenticate({type: 'token', token: githubToken}); | ||||||
|     if (err) return cb(err) |  | ||||||
| 
 | 
 | ||||||
|     gitHead(function (err, hash) { |   if (debug) { | ||||||
|       if (err) return cb(err) |     await github.repos.createRelease(release); | ||||||
|  |     return {published: true, release}; | ||||||
|  |   } | ||||||
| 
 | 
 | ||||||
|       var ghRepo = parseSlug(pkg.repository.url) |   await github.gitdata.createReference(tag); | ||||||
|       var tag = { |   await github.repos.createRelease(release); | ||||||
|         owner: ghRepo[0], |  | ||||||
|         repo: ghRepo[1], |  | ||||||
|         ref: 'refs/tags/v' + pkg.version, |  | ||||||
|         sha: hash |  | ||||||
|       } |  | ||||||
|       var release = { |  | ||||||
|         owner: ghRepo[0], |  | ||||||
|         repo: ghRepo[1], |  | ||||||
|         tag_name: 'v' + pkg.version, |  | ||||||
|         name: 'v' + pkg.version, |  | ||||||
|         target_commitish: options.branch, |  | ||||||
|         draft: !!options.debug, |  | ||||||
|         body: log |  | ||||||
|       } |  | ||||||
| 
 | 
 | ||||||
|       if (options.debug && !options.githubToken) { |   return {published: true, release}; | ||||||
|         return cb(null, false, release) | }; | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       github.authenticate({ |  | ||||||
|         type: 'token', |  | ||||||
|         token: options.githubToken |  | ||||||
|       }) |  | ||||||
| 
 |  | ||||||
|       if (options.debug) { |  | ||||||
|         return github.repos.createRelease(release, function (err) { |  | ||||||
|           if (err) return cb(err) |  | ||||||
| 
 |  | ||||||
|           cb(null, true, release) |  | ||||||
|         }) |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       github.gitdata.createReference(tag, function (err) { |  | ||||||
|         if (err) return cb(err) |  | ||||||
| 
 |  | ||||||
|         github.repos.createRelease(release, function (err) { |  | ||||||
|           if (err) return cb(err) |  | ||||||
| 
 |  | ||||||
|           cb(null, true, release) |  | ||||||
|         }) |  | ||||||
|       }) |  | ||||||
|     }) |  | ||||||
|   }) |  | ||||||
| } |  | ||||||
|  | |||||||
							
								
								
									
										58
									
								
								src/pre.js
									
									
									
									
									
								
							
							
						
						
									
										58
									
								
								src/pre.js
									
									
									
									
									
								
							| @ -1,45 +1,23 @@ | |||||||
| var _ = require('lodash') | const {promisify} = require('util'); | ||||||
| var auto = require('run-auto') | const {assign} = require('lodash'); | ||||||
| var semver = require('semver') | const semver = require('semver'); | ||||||
| 
 | 
 | ||||||
| var getCommits = require('./lib/commits') | const getCommits = require('./lib/get-commits'); | ||||||
| var getType = require('./lib/type') | const getReleaseType = require('./lib/get-release-type'); | ||||||
| 
 | 
 | ||||||
| module.exports = function (config, cb) { | module.exports = async config => { | ||||||
|   var plugins = config.plugins |   const {getLastRelease, verifyRelease} = config.plugins; | ||||||
| 
 | 
 | ||||||
|   auto({ |   const lastRelease = await promisify(getLastRelease)(config); | ||||||
|     lastRelease: plugins.getLastRelease.bind(null, config), |   const commits = await getCommits(assign({lastRelease}, config)); | ||||||
|     commits: ['lastRelease', function (results, cb) { |   const type = await getReleaseType(assign({commits, lastRelease}, config)); | ||||||
|       getCommits(_.assign({ |  | ||||||
|         lastRelease: results.lastRelease |  | ||||||
|       }, config), |  | ||||||
|       cb) |  | ||||||
|     }], |  | ||||||
|     type: ['commits', 'lastRelease', function (results, cb) { |  | ||||||
|       getType(_.assign({ |  | ||||||
|         commits: results.commits, |  | ||||||
|         lastRelease: results.lastRelease |  | ||||||
|       }, config), |  | ||||||
|       cb) |  | ||||||
|     }] |  | ||||||
|   }, function (err, results) { |  | ||||||
|     if (err) return cb(err) |  | ||||||
| 
 | 
 | ||||||
|     var nextRelease = { |   const nextRelease = { | ||||||
|       type: results.type, |     type: type, | ||||||
|       version: results.type === 'initial' |     version: type === 'initial' ? '1.0.0' : semver.inc(lastRelease.version, type), | ||||||
|         ? '1.0.0' |   }; | ||||||
|         : semver.inc(results.lastRelease.version, results.type) |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     plugins.verifyRelease(_.assign({ |   await promisify(verifyRelease)(assign({commits, lastRelease, nextRelease}, config)); | ||||||
|       commits: results.commits, | 
 | ||||||
|       lastRelease: results.lastRelease, |   return nextRelease; | ||||||
|       nextRelease: nextRelease | }; | ||||||
|     }, config), function (err) { |  | ||||||
|       if (err) return cb(err) |  | ||||||
|       cb(null, nextRelease) |  | ||||||
|     }) |  | ||||||
|   }) |  | ||||||
| } |  | ||||||
|  | |||||||
							
								
								
									
										3
									
								
								test/fixtures/plugin-error-a.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								test/fixtures/plugin-error-a.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | |||||||
|  | module.exports = function(config, options, cb) { | ||||||
|  |   cb(new Error('a')); | ||||||
|  | }; | ||||||
							
								
								
									
										3
									
								
								test/fixtures/plugin-error-b.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								test/fixtures/plugin-error-b.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | |||||||
|  | module.exports = function(config, options, cb) { | ||||||
|  |   cb(new Error('b')); | ||||||
|  | }; | ||||||
							
								
								
									
										3
									
								
								test/fixtures/plugin-result-a.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								test/fixtures/plugin-result-a.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | |||||||
|  | module.exports = function(config, options, cb) { | ||||||
|  |   cb(null, 'a'); | ||||||
|  | }; | ||||||
							
								
								
									
										3
									
								
								test/fixtures/plugin-result-b.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								test/fixtures/plugin-result-b.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | |||||||
|  | module.exports = function(config, options, cb) { | ||||||
|  |   cb(null, 'b'); | ||||||
|  | }; | ||||||
							
								
								
									
										3
									
								
								test/fixtures/plugin-result-config.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								test/fixtures/plugin-result-config.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | |||||||
|  | module.exports = function(pluginConfig, options, cb) { | ||||||
|  |   cb(null, {pluginConfig, options}); | ||||||
|  | }; | ||||||
							
								
								
									
										130
									
								
								test/get-commits.test.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								test/get-commits.test.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,130 @@ | |||||||
|  | import test from 'ava'; | ||||||
|  | import {gitRepo, gitCommits, gitCheckout} from './helpers/git-utils'; | ||||||
|  | import proxyquire from 'proxyquire'; | ||||||
|  | import {stub} from 'sinon'; | ||||||
|  | import SemanticReleaseError from '@semantic-release/error'; | ||||||
|  | 
 | ||||||
|  | // Stub to capture the log messages
 | ||||||
|  | const errorLog = stub(); | ||||||
|  | // Module to test
 | ||||||
|  | const getCommits = proxyquire('../src/lib/get-commits', {npmlog: {error: errorLog}}); | ||||||
|  | 
 | ||||||
|  | test.beforeEach(t => { | ||||||
|  |   // Save the current working diretory
 | ||||||
|  |   t.context.cwd = process.cwd(); | ||||||
|  |   // Reset the stub call history
 | ||||||
|  |   errorLog.resetHistory(); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test.afterEach.always(t => { | ||||||
|  |   // Restore the current working directory
 | ||||||
|  |   process.chdir(t.context.cwd); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test.serial('Get all commits when there is no last release', async t => { | ||||||
|  |   // Create a git repository, set the current working directory at the root of the repo
 | ||||||
|  |   await gitRepo(); | ||||||
|  |   // Add commits to the master branch
 | ||||||
|  |   const commits = await gitCommits(['fix: First fix', 'feat: Second feature']); | ||||||
|  | 
 | ||||||
|  |   // Retrieve the commits with the commits module
 | ||||||
|  |   const result = await getCommits({lastRelease: {}, options: {branch: 'master'}}); | ||||||
|  | 
 | ||||||
|  |   // The commits created and and retrieved by the module are identical
 | ||||||
|  |   t.is(result.length, 2); | ||||||
|  |   t.is(result[0].hash.substring(0, 7), commits[0].hash); | ||||||
|  |   t.is(result[0].message, commits[0].message); | ||||||
|  |   t.is(result[1].hash.substring(0, 7), commits[1].hash); | ||||||
|  |   t.is(result[1].message, commits[1].message); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test.serial('Get all commits since lastRelease gitHead', async t => { | ||||||
|  |   // Create a git repository, set the current working directory at the root of the repo
 | ||||||
|  |   await gitRepo(); | ||||||
|  |   // Add commits to the master branch
 | ||||||
|  |   const commits = await gitCommits(['fix: First fix', 'feat: Second feature', 'feat: Third feature']); | ||||||
|  | 
 | ||||||
|  |   // Retrieve the commits with the commits module
 | ||||||
|  |   const result = await getCommits({ | ||||||
|  |     lastRelease: {gitHead: commits[commits.length - 1].hash}, | ||||||
|  |     options: {branch: 'master'}, | ||||||
|  |   }); | ||||||
|  |   // The commits created and retrieved by the module are identical
 | ||||||
|  |   t.is(result.length, 2); | ||||||
|  |   t.is(result[0].hash.substring(0, 7), commits[0].hash); | ||||||
|  |   t.is(result[0].message, commits[0].message); | ||||||
|  |   t.is(result[1].hash.substring(0, 7), commits[1].hash); | ||||||
|  |   t.is(result[1].message, commits[1].message); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test.serial('Return empty array if there is no commits', async t => { | ||||||
|  |   // Create a git repository, set the current working directory at the root of the repo
 | ||||||
|  |   await gitRepo(); | ||||||
|  |   // Add commits to the master branch
 | ||||||
|  |   const commits = await gitCommits(['fix: First fix', 'feat: Second feature']); | ||||||
|  | 
 | ||||||
|  |   // Retrieve the commits with the commits module
 | ||||||
|  |   const result = await getCommits({lastRelease: {gitHead: commits[0].hash}, options: {branch: 'master'}}); | ||||||
|  | 
 | ||||||
|  |   // Verify no commit is retrieved
 | ||||||
|  |   t.deepEqual(result, []); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test.serial('Return empty array if lastRelease.gitHead is the last commit', async t => { | ||||||
|  |   // Create a git repository, set the current working directory at the root of the repo
 | ||||||
|  |   await gitRepo(); | ||||||
|  | 
 | ||||||
|  |   // Retrieve the commits with the commits module
 | ||||||
|  |   const result = await getCommits({lastRelease: {}, options: {branch: 'master'}}); | ||||||
|  | 
 | ||||||
|  |   // Verify no commit is retrieved
 | ||||||
|  |   t.deepEqual(result, []); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test.serial('Throws ENOTINHISTORY error if gitHead is not in history', async t => { | ||||||
|  |   // Create a git repository, set the current working directory at the root of the repo
 | ||||||
|  |   await gitRepo(); | ||||||
|  |   // Add commits to the master branch
 | ||||||
|  |   await gitCommits(['fix: First fix', 'feat: Second feature']); | ||||||
|  | 
 | ||||||
|  |   // Retrieve the commits with the commits module
 | ||||||
|  |   const error = await t.throws(getCommits({lastRelease: {gitHead: 'notinhistory'}, options: {branch: 'master'}})); | ||||||
|  | 
 | ||||||
|  |   // Verify error code and message
 | ||||||
|  |   t.is(error.code, 'ENOTINHISTORY'); | ||||||
|  |   t.true(error instanceof SemanticReleaseError); | ||||||
|  | 
 | ||||||
|  |   // Verify the log function has been called with a message mentionning the branch
 | ||||||
|  |   t.regex(errorLog.firstCall.args[1], /history of the "master" branch/); | ||||||
|  |   // Verify the log function has been called with a message mentionning the missing gitHead
 | ||||||
|  |   t.regex(errorLog.firstCall.args[1], /restoring the commit "notinhistory"/); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test.serial('Throws ENOTINHISTORY error if gitHead is not in branch history but present in others', async t => { | ||||||
|  |   // Create a git repository, set the current working directory at the root of the repo
 | ||||||
|  |   await gitRepo(); | ||||||
|  |   // Add commits to the master branch
 | ||||||
|  |   await gitCommits(['First', 'Second']); | ||||||
|  |   // Create the new branch 'other-branch' from master
 | ||||||
|  |   await gitCheckout('other-branch', true); | ||||||
|  |   // Add commits to the 'other-branch' branch
 | ||||||
|  |   const commitsBranch = await gitCommits(['Third', 'Fourth']); | ||||||
|  |   // Create the new branch 'another-branch' from 'other-branch'
 | ||||||
|  |   await gitCheckout('another-branch', true); | ||||||
|  | 
 | ||||||
|  |   // Retrieve the commits with the commits module
 | ||||||
|  |   const error = await t.throws( | ||||||
|  |     getCommits({lastRelease: {version: '1.0.1', gitHead: commitsBranch[0].hash}, options: {branch: 'master'}}) | ||||||
|  |   ); | ||||||
|  | 
 | ||||||
|  |   // Verify error code and message
 | ||||||
|  |   t.is(error.code, 'ENOTINHISTORY'); | ||||||
|  |   t.true(error instanceof SemanticReleaseError); | ||||||
|  | 
 | ||||||
|  |   // Verify the log function has been called with a message mentionning the branch
 | ||||||
|  |   t.regex(errorLog.firstCall.args[1], /history of the "master" branch/); | ||||||
|  |   // Verify the log function has been called with a message mentionning the missing gitHead
 | ||||||
|  |   t.regex(errorLog.firstCall.args[1], new RegExp(`restoring the commit "${commitsBranch[0].hash}"`)); | ||||||
|  |   // Verify the log function has been called with a message mentionning the branches that contains the gitHead
 | ||||||
|  |   t.regex(errorLog.firstCall.args[1], /\* another-branch\s+\* other-branch/); | ||||||
|  | }); | ||||||
							
								
								
									
										82
									
								
								test/get-registry.test.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								test/get-registry.test.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,82 @@ | |||||||
|  | import test from 'ava'; | ||||||
|  | import {stub} from 'sinon'; | ||||||
|  | const getRegistry = require('../src/lib/get-registry'); | ||||||
|  | 
 | ||||||
|  | test('Get registry from package.json', t => { | ||||||
|  |   // Retrieve the registry with the get-registry module and verify it returns the one from the package.json in parameter
 | ||||||
|  |   t.is(getRegistry({name: 'publish-config', publishConfig: {registry: 'a'}}, {}), 'a'); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test('Prioritize the package.json registry config', t => { | ||||||
|  |   // Stub the npmconf object
 | ||||||
|  |   const get = stub(); | ||||||
|  | 
 | ||||||
|  |   // Retrieve the registry with the get-registry module and verify it returns the one from the package.json in parameter
 | ||||||
|  |   t.is(getRegistry({name: 'publish-config', publishConfig: {registry: 'b'}}, {get}), 'b'); | ||||||
|  | 
 | ||||||
|  |   // Verify the registry has been retrieved from the package.json without trying the stubbed npmconf
 | ||||||
|  |   t.true(get.notCalled); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test('Get registry for regular package name', t => { | ||||||
|  |   // Stub the npmconf object returns 'b' for 'registry' property
 | ||||||
|  |   const get = stub() | ||||||
|  |     .withArgs('registry') | ||||||
|  |     .returns('b'); | ||||||
|  | 
 | ||||||
|  |   // Retrieve the registry with the get-registry module and verify it returns the one configured in the stubbed npmconf
 | ||||||
|  |   t.is(getRegistry({name: 'normal'}, {get}), 'b'); | ||||||
|  | 
 | ||||||
|  |   // Verify the registry has been retrieved by calling the stubbed npmconf
 | ||||||
|  |   t.true(get.calledWithExactly('registry')); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test('Get default registry', t => { | ||||||
|  |   // Stub the npmconf object, returns 'null'
 | ||||||
|  |   const get = stub().returns(null); | ||||||
|  | 
 | ||||||
|  |   // Retrieve the registry with the get-registry module and verify it returns default one
 | ||||||
|  |   t.is(getRegistry({name: 'normal'}, {get}), 'https://registry.npmjs.org/'); | ||||||
|  | 
 | ||||||
|  |   // Verify the module tried first to retrieve the registry by calling the stubbed npmconf
 | ||||||
|  |   t.true(get.calledWithExactly('registry')); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test('Get registry for scoped package name', t => { | ||||||
|  |   // Stub the npmconf object, returns 'c' for '@scoped/registry' property
 | ||||||
|  |   const get = stub() | ||||||
|  |     .withArgs('@scoped/registry') | ||||||
|  |     .returns('c'); | ||||||
|  | 
 | ||||||
|  |   // Retrieve the registry with the get-registry module and verify it returns the one configured in the stubbed npmconf
 | ||||||
|  |   t.is(getRegistry({name: '@scoped/foo'}, {get}), 'c'); | ||||||
|  | 
 | ||||||
|  |   // Verify the registry for the scope '@scoped' has been retrieved by calling the stubbed npmconf
 | ||||||
|  |   t.true(get.calledWithExactly('@scoped/registry')); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test('Get regular registry for scoped package name', t => { | ||||||
|  |   // Stub the npmconf object, returns 'd' for 'registry' property
 | ||||||
|  |   const get = stub() | ||||||
|  |     .withArgs('registry') | ||||||
|  |     .returns('d'); | ||||||
|  | 
 | ||||||
|  |   // Retrieve the registry with the get-registry module and verify it returns the regular default one for `@scoped` packages
 | ||||||
|  |   t.is(getRegistry({name: '@scoped/baz'}, {get}), 'd'); | ||||||
|  | 
 | ||||||
|  |   // Verify the module tried to retrieve the @scoped registry by calling the stubbed npmconf
 | ||||||
|  |   t.true(get.calledWithExactly('@scoped/registry')); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test('Get default registry for scoped package name', t => { | ||||||
|  |   // Stub the npmconf object, returns 'd' for 'registry' property
 | ||||||
|  |   const get = stub().returns(null); | ||||||
|  | 
 | ||||||
|  |   // Retrieve the registry with the get-registry module and verify it returns default one for `@scoped` packages
 | ||||||
|  |   t.is(getRegistry({name: '@scoped/baz'}, {get}), 'https://registry.npmjs.org/'); | ||||||
|  | 
 | ||||||
|  |   // Verify the module tried to retrieve the @scoped registry by calling the stubbed npmconf
 | ||||||
|  |   t.true(get.calledWithExactly('@scoped/registry')); | ||||||
|  |   // Verify the module tried to retrieve the regular registry by calling the stubbed npmconf
 | ||||||
|  |   t.true(get.calledWithExactly('registry')); | ||||||
|  | }); | ||||||
							
								
								
									
										87
									
								
								test/get-release-type.test.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								test/get-release-type.test.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,87 @@ | |||||||
|  | import {callbackify} from 'util'; | ||||||
|  | import test from 'ava'; | ||||||
|  | import {stub} from 'sinon'; | ||||||
|  | import SemanticReleaseError from '@semantic-release/error'; | ||||||
|  | import getReleaseType from '../src/lib/get-release-type'; | ||||||
|  | 
 | ||||||
|  | test('Get commit types from commits', async t => { | ||||||
|  |   // Stub the commitAnalyzer plugin, returns 'major' release type
 | ||||||
|  |   const analyzeCommits = stub().resolves('major'); | ||||||
|  |   const commits = [{hash: '0', message: 'a'}]; | ||||||
|  | 
 | ||||||
|  |   // Call the get-release-type module
 | ||||||
|  |   const releaseType = await getReleaseType({ | ||||||
|  |     commits, | ||||||
|  |     lastRelease: {version: '1.0.0'}, | ||||||
|  |     plugins: {analyzeCommits: callbackify(analyzeCommits)}, | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   // Verify the module return the release type obtain from the commitAnalyzer plugin
 | ||||||
|  |   t.is(releaseType, 'major'); | ||||||
|  | 
 | ||||||
|  |   // Verify the commitAnalyzer plugin was called with the commits
 | ||||||
|  |   t.true(analyzeCommits.calledOnce); | ||||||
|  |   t.deepEqual(analyzeCommits.firstCall.args[0].commits, commits); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test('Throws error when no changes', async t => { | ||||||
|  |   // Stub the commitAnalyzer plugin, returns 'null' release type
 | ||||||
|  |   const analyzeCommits = stub().resolves(null); | ||||||
|  |   const commits = [{hash: '0', message: 'a'}]; | ||||||
|  | 
 | ||||||
|  |   // Call the get-release-type module and verify it returns an error
 | ||||||
|  |   const error = await t.throws( | ||||||
|  |     getReleaseType({ | ||||||
|  |       commits, | ||||||
|  |       lastRelease: {version: '1.0.0'}, | ||||||
|  |       plugins: {analyzeCommits: callbackify(analyzeCommits)}, | ||||||
|  |     }) | ||||||
|  |   ); | ||||||
|  | 
 | ||||||
|  |   // Verify the error code adn type
 | ||||||
|  |   t.is(error.code, 'ENOCHANGE'); | ||||||
|  |   t.true(error instanceof SemanticReleaseError); | ||||||
|  | 
 | ||||||
|  |   // Verify the commitAnalyzer plugin was called with the commits
 | ||||||
|  |   t.true(analyzeCommits.calledOnce); | ||||||
|  |   t.deepEqual(analyzeCommits.firstCall.args[0].commits, commits); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test('Return initial if there is no lastRelease', async t => { | ||||||
|  |   // Stub the commitAnalyzer plugin, returns 'major' release type
 | ||||||
|  |   const analyzeCommits = stub().resolves('major'); | ||||||
|  |   const commits = [{hash: '0', message: 'a'}]; | ||||||
|  | 
 | ||||||
|  |   // Call the get-release-type module
 | ||||||
|  |   const releaseType = await getReleaseType({ | ||||||
|  |     commits, | ||||||
|  |     lastRelease: {}, | ||||||
|  |     plugins: {analyzeCommits: callbackify(analyzeCommits)}, | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   // Verify the module return an initial release type
 | ||||||
|  |   t.is(releaseType, 'initial'); | ||||||
|  | 
 | ||||||
|  |   // Verify the commitAnalyzer plugin was called with the commits
 | ||||||
|  |   t.true(analyzeCommits.calledOnce); | ||||||
|  |   t.deepEqual(analyzeCommits.firstCall.args[0].commits, commits); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test('Throws error when no changes even if there is no lastRelease', async t => { | ||||||
|  |   // Stub the commitAnalyzer plugin, returns 'null' release type
 | ||||||
|  |   const analyzeCommits = stub().resolves(null); | ||||||
|  |   const commits = [{hash: '0', message: 'a'}]; | ||||||
|  | 
 | ||||||
|  |   // Call the get-release-type module and verify it returns an error
 | ||||||
|  |   const error = await t.throws( | ||||||
|  |     getReleaseType({commits, lastRelease: {}, plugins: {analyzeCommits: callbackify(analyzeCommits)}}) | ||||||
|  |   ); | ||||||
|  | 
 | ||||||
|  |   // Verify the error code adn type
 | ||||||
|  |   t.is(error.code, 'ENOCHANGE'); | ||||||
|  |   t.true(error instanceof SemanticReleaseError); | ||||||
|  | 
 | ||||||
|  |   // Verify the commitAnalyzer plugin was called with the commits
 | ||||||
|  |   t.true(analyzeCommits.calledOnce); | ||||||
|  |   t.deepEqual(analyzeCommits.firstCall.args[0].commits, commits); | ||||||
|  | }); | ||||||
							
								
								
									
										61
									
								
								test/helpers/git-utils.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								test/helpers/git-utils.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,61 @@ | |||||||
|  | import {mkdir} from 'fs-extra'; | ||||||
|  | import tempy from 'tempy'; | ||||||
|  | import execa from 'execa'; | ||||||
|  | import pMapSeries from 'p-map-series'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Commit message informations. | ||||||
|  |  *  | ||||||
|  |  * @typedef {Object} Commit | ||||||
|  |  * @property {string} branch The commit branch | ||||||
|  |  * @property {string} hash The commit hash | ||||||
|  |  * @property {string} message The commit message | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |   * Create a temporary git repository. | ||||||
|  |   * | ||||||
|  |   * @method gitCommits | ||||||
|  |   * @param {Array<Commit>} commits the created commits. | ||||||
|  |   */ | ||||||
|  | export async function gitRepo() { | ||||||
|  |   const dir = tempy.directory(); | ||||||
|  | 
 | ||||||
|  |   process.chdir(dir); | ||||||
|  |   await mkdir('git-templates'); | ||||||
|  |   await execa('git', ['init', '--template=./git-templates']); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Create commits on the current git repository. | ||||||
|  |  * | ||||||
|  |  * @method gitCommits | ||||||
|  |  * @param {Array<String>} messages commit messages | ||||||
|  |  * @returns {Array<Commit>} commits the created commits, in reverse order (to match `git log` order) | ||||||
|  |  */ | ||||||
|  | export async function gitCommits(messages) { | ||||||
|  |   return (await pMapSeries(messages, async msg => { | ||||||
|  |     const {stdout} = await execa('git', ['commit', '-m', msg, '--allow-empty', '--no-gpg-sign']); | ||||||
|  |     const [, branch, hash, message] = /^\[(\w+)\(?.*?\)?(\w+)\] (.+)$/.exec(stdout); | ||||||
|  |     return {branch, hash, message}; | ||||||
|  |   })).reverse(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Checkout a branch on the current git repository. | ||||||
|  |  * | ||||||
|  |  * @param {String} branch Branch name | ||||||
|  |  * @param {Boolean} create `true` to create the branche ans switch, `false` to only switch | ||||||
|  |  */ | ||||||
|  | export async function gitCheckout(branch, create) { | ||||||
|  |   await execa('git', ['checkout', create ? '-b' : null, branch]); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Get the sha of the head commit in the current git repository. | ||||||
|  |  *  | ||||||
|  |  * @return {String} The sha of the head commit in the current git repository. | ||||||
|  |  */ | ||||||
|  | export async function gitHead() { | ||||||
|  |   return (await execa('git', ['rev-parse', 'HEAD'])).stdout; | ||||||
|  | } | ||||||
							
								
								
									
										7
									
								
								test/helpers/mock-github.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								test/helpers/mock-github.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | |||||||
|  | import nock from 'nock'; | ||||||
|  | 
 | ||||||
|  | export function authenticate( | ||||||
|  |   {githubToken = 'GH_TOKEN', githubUrl = 'https://api.github.com', githubApiPathPrefix = ''} = {} | ||||||
|  | ) { | ||||||
|  |   return nock(`${githubUrl}/${githubApiPathPrefix}`, {reqheaders: {Authorization: `token ${githubToken}`}}); | ||||||
|  | } | ||||||
							
								
								
									
										14
									
								
								test/helpers/registry/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								test/helpers/registry/index.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,14 @@ | |||||||
|  | import execa from 'execa'; | ||||||
|  | 
 | ||||||
|  | const opts = {cwd: __dirname}; | ||||||
|  | 
 | ||||||
|  | export const uri = | ||||||
|  |   'http://localhost:' + (process.env.TRAVIS === 'true' ? 5984 : 15986) + '/registry/_design/app/_rewrite/'; | ||||||
|  | 
 | ||||||
|  | export function start() { | ||||||
|  |   return execa('./start.sh', opts); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function stop() { | ||||||
|  |   return execa('./stop.sh', opts); | ||||||
|  | } | ||||||
| @ -2,6 +2,7 @@ | |||||||
| database_dir = data | database_dir = data | ||||||
| view_index_dir = data | view_index_dir = data | ||||||
| delayed_commits = false | delayed_commits = false | ||||||
|  | uuid = bf4ecd84a7c89d60b5b2540fdf8c322c | ||||||
| 
 | 
 | ||||||
| [couch_httpd_auth] | [couch_httpd_auth] | ||||||
| public_fields = appdotnet, avatar, avatarMedium, avatarLarge, date, email, fields, freenode, fullname, github, homepage, name, roles, twitter, type, _id, _rev | public_fields = appdotnet, avatar, avatarMedium, avatarLarge, date, email, fields, freenode, fullname, github, homepage, name, roles, twitter, type, _id, _rev | ||||||
							
								
								
									
										140
									
								
								test/integration.test.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								test/integration.test.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,140 @@ | |||||||
|  | import test from 'ava'; | ||||||
|  | import {writeJson, readJson} from 'fs-extra'; | ||||||
|  | import {start, stop, uri} from './helpers/registry'; | ||||||
|  | import {gitRepo, gitCommits, gitHead} from './helpers/git-utils'; | ||||||
|  | import execa from 'execa'; | ||||||
|  | 
 | ||||||
|  | test.beforeEach(async t => { | ||||||
|  |   // Save the current working diretory
 | ||||||
|  |   t.context.cwd = process.cwd(); | ||||||
|  |   // Start the local NPM registry
 | ||||||
|  |   await start(); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test.afterEach.always(async t => { | ||||||
|  |   // Restore the current working directory
 | ||||||
|  |   process.chdir(t.context.cwd); | ||||||
|  |   // Stop the local NPM registry
 | ||||||
|  |   await stop(); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test.serial('Release patch, minor and major versions', async t => { | ||||||
|  |   // Environment variables used with cli
 | ||||||
|  |   const env = { | ||||||
|  |     CI: true, | ||||||
|  |     npm_config_registry: uri, | ||||||
|  |     GH_TOKEN: 'github_token', | ||||||
|  |     NPM_OLD_TOKEN: 'aW50ZWdyYXRpb246c3VjaHNlY3VyZQ==', | ||||||
|  |     NPM_EMAIL: 'integration@test.com', | ||||||
|  |   }; | ||||||
|  |   // Create a git repository, set the current working directory at the root of the repo
 | ||||||
|  |   t.log('Create git repository'); | ||||||
|  |   await gitRepo(); | ||||||
|  | 
 | ||||||
|  |   // Create package.json in repository root
 | ||||||
|  |   await writeJson('./package.json', { | ||||||
|  |     name: 'test-module', | ||||||
|  |     version: '0.0.0-dev', | ||||||
|  |     repository: {url: 'git+https://github.com/semantic-release/test-module'}, | ||||||
|  |     release: {verifyConditions: require.resolve('../src/lib/plugin-noop')}, | ||||||
|  |   }); | ||||||
|  |   // Create a npm-shrinkwrap.json file
 | ||||||
|  |   await execa('npm', ['shrinkwrap'], {env}); | ||||||
|  | 
 | ||||||
|  |   /** No release **/ | ||||||
|  | 
 | ||||||
|  |   t.log('Commit a chore'); | ||||||
|  |   await gitCommits(['chore: Init repository']); | ||||||
|  |   t.log('$ semantic-release pre'); | ||||||
|  |   let {stdout, stderr, code} = await t.throws(execa(require.resolve('../bin/semantic-release'), ['pre'], {env})); | ||||||
|  |   t.regex(stderr, /ENOCHANGE There are no relevant changes, so no new version is released/); | ||||||
|  |   t.is(code, 1); | ||||||
|  | 
 | ||||||
|  |   /** Minor release **/ | ||||||
|  | 
 | ||||||
|  |   t.log('Commit a feature'); | ||||||
|  |   await gitCommits(['feat: Initial commit']); | ||||||
|  |   t.log('$ semantic-release pre'); | ||||||
|  |   ({stdout, stderr, code} = await execa(require.resolve('../bin/semantic-release'), ['pre'], {env})); | ||||||
|  |   // Verify package.json and npm-shrinkwrap.json have been updated
 | ||||||
|  |   t.is((await readJson('./package.json')).version, '1.0.0'); | ||||||
|  |   t.is((await readJson('./npm-shrinkwrap.json')).version, '1.0.0'); | ||||||
|  |   t.log('$ npm publish'); | ||||||
|  |   ({stdout, stderr, code} = await execa('npm', ['publish'], {env})); | ||||||
|  |   // Verify output of npm publish
 | ||||||
|  |   t.regex(stdout, /test-module@1.0.0/); | ||||||
|  |   t.is(code, 0); | ||||||
|  |   // Retrieve the published package from the registry and check version and gitHead
 | ||||||
|  |   let [, version, releaseGitHead] = /^version = '(.+)'\s+gitHead = '(.+)'$/.exec( | ||||||
|  |     (await execa('npm', ['show', 'test-module', 'version', 'gitHead'], {env})).stdout | ||||||
|  |   ); | ||||||
|  |   t.is(version, '1.0.0'); | ||||||
|  |   t.is(releaseGitHead, await gitHead()); | ||||||
|  |   t.log(`+ released ${version} with gitHead ${releaseGitHead}`); | ||||||
|  | 
 | ||||||
|  |   /** Patch release **/ | ||||||
|  | 
 | ||||||
|  |   t.log('Commit a fix'); | ||||||
|  |   await gitCommits(['fix: bar']); | ||||||
|  |   t.log('$ semantic-release pre'); | ||||||
|  |   ({stdout, stderr, code} = await execa(require.resolve('../bin/semantic-release'), ['pre'], {env})); | ||||||
|  |   // Verify package.json and npm-shrinkwrap.json have been updated
 | ||||||
|  |   t.is((await readJson('./package.json')).version, '1.0.1'); | ||||||
|  |   t.is((await readJson('./npm-shrinkwrap.json')).version, '1.0.1'); | ||||||
|  |   t.log('$ npm publish'); | ||||||
|  |   ({stdout, stderr, code} = await execa('npm', ['publish'], {env})); | ||||||
|  |   // Verify output of npm publish
 | ||||||
|  |   t.regex(stdout, /test-module@1.0.1/); | ||||||
|  |   t.is(code, 0); | ||||||
|  |   // Retrieve the published package from the registry and check version and gitHead
 | ||||||
|  |   [, version, releaseGitHead] = /^version = '(.+)'\s+gitHead = '(.+)'$/.exec( | ||||||
|  |     (await execa('npm', ['show', 'test-module', 'version', 'gitHead'], {env})).stdout | ||||||
|  |   ); | ||||||
|  |   t.is(version, '1.0.1'); | ||||||
|  |   t.is(releaseGitHead, await gitHead()); | ||||||
|  |   t.log(`+ released ${version} with gitHead ${releaseGitHead}`); | ||||||
|  | 
 | ||||||
|  |   /** Minor release **/ | ||||||
|  | 
 | ||||||
|  |   t.log('Commit a feature'); | ||||||
|  |   await gitCommits(['feat: baz']); | ||||||
|  |   t.log('$ semantic-release pre'); | ||||||
|  |   ({stdout, stderr, code} = await execa(require.resolve('../bin/semantic-release'), ['pre'], {env})); | ||||||
|  |   // Verify package.json and npm-shrinkwrap.json have been updated
 | ||||||
|  |   t.is((await readJson('./package.json')).version, '1.1.0'); | ||||||
|  |   t.is((await readJson('./npm-shrinkwrap.json')).version, '1.1.0'); | ||||||
|  |   t.log('$ npm publish'); | ||||||
|  |   ({stdout, stderr, code} = await execa('npm', ['publish'], {env})); | ||||||
|  |   // Verify output of npm publish
 | ||||||
|  |   t.regex(stdout, /test-module@1.1.0/); | ||||||
|  |   t.is(code, 0); | ||||||
|  |   // Retrieve the published package from the registry and check version and gitHead
 | ||||||
|  |   [, version, releaseGitHead] = /^version = '(.+)'\s+gitHead = '(.+)'$/.exec( | ||||||
|  |     (await execa('npm', ['show', 'test-module', 'version', 'gitHead'], {env})).stdout | ||||||
|  |   ); | ||||||
|  |   t.is(version, '1.1.0'); | ||||||
|  |   t.is(releaseGitHead, await gitHead()); | ||||||
|  |   t.log(`+ released ${version} with gitHead ${releaseGitHead}`); | ||||||
|  | 
 | ||||||
|  |   /** Major release **/ | ||||||
|  | 
 | ||||||
|  |   t.log('Commit a breaking change'); | ||||||
|  |   await gitCommits(['feat: foo\n\n BREAKING CHANGE: bar']); | ||||||
|  |   t.log('$ semantic-release pre'); | ||||||
|  |   ({stdout, stderr, code} = await execa(require.resolve('../bin/semantic-release'), ['pre'], {env})); | ||||||
|  |   // Verify package.json and npm-shrinkwrap.json have been updated
 | ||||||
|  |   t.is((await readJson('./package.json')).version, '2.0.0'); | ||||||
|  |   t.is((await readJson('./npm-shrinkwrap.json')).version, '2.0.0'); | ||||||
|  |   t.log('$ npm publish'); | ||||||
|  |   ({stdout, stderr, code} = await execa('npm', ['publish'], {env})); | ||||||
|  |   // Verify output of npm publish
 | ||||||
|  |   t.regex(stdout, /test-module@2.0.0/); | ||||||
|  |   t.is(code, 0); | ||||||
|  |   // Retrieve the published package from the registry and check version and gitHead
 | ||||||
|  |   [, version, releaseGitHead] = /^version = '(.+)'\s+gitHead = '(.+)'$/.exec( | ||||||
|  |     (await execa('npm', ['show', 'test-module', 'version', 'gitHead'], {env})).stdout | ||||||
|  |   ); | ||||||
|  |   t.is(version, '2.0.0'); | ||||||
|  |   t.is(releaseGitHead, await gitHead()); | ||||||
|  |   t.log(`+ released ${version} with gitHead ${releaseGitHead}`); | ||||||
|  | }); | ||||||
| @ -1,12 +0,0 @@ | |||||||
| var nixt = require('nixt') |  | ||||||
| 
 |  | ||||||
| module.exports = function (cwd, uri) { |  | ||||||
|   return nixt() |  | ||||||
|     .cwd(cwd) |  | ||||||
|     .env('NPM_OLD_TOKEN', 'aW50ZWdyYXRpb246c3VjaHNlY3VyZQ==') |  | ||||||
|     .env('NPM_EMAIL', 'integration@test.com') |  | ||||||
|     .env('GH_TOKEN', 'ghtoken') |  | ||||||
|     .env('CI', 'true') |  | ||||||
|     .env('npm_config_registry', uri) |  | ||||||
|     .clone() |  | ||||||
| } |  | ||||||
| @ -1,36 +0,0 @@ | |||||||
| var exec = require('child_process').exec |  | ||||||
| var join = require('path').join |  | ||||||
| var writeFileSync = require('fs').writeFileSync |  | ||||||
| 
 |  | ||||||
| var mkdirp = require('mkdirp') |  | ||||||
| 
 |  | ||||||
| module.exports = function (name, registry, cb) { |  | ||||||
|   var cwd = join(__dirname, '../tmp', name) |  | ||||||
| 
 |  | ||||||
|   mkdirp.sync(cwd) |  | ||||||
| 
 |  | ||||||
|   writeFileSync(join(cwd, 'package.json'), JSON.stringify({ |  | ||||||
|     name: name, |  | ||||||
|     repository: { |  | ||||||
|       url: 'git+https://github.com/semantic-release/test' |  | ||||||
|     }, |  | ||||||
|     release: { |  | ||||||
|       verifyConditions: '../../../src/lib/plugin-noop' |  | ||||||
|     } |  | ||||||
|   }, null, 2)) |  | ||||||
| 
 |  | ||||||
|   exec( |  | ||||||
|   'git init && ' + |  | ||||||
|   'git config user.email "integration@test" && ' + |  | ||||||
|   'git config user.name "Integration Test" && ' + |  | ||||||
|   'git add . && ' + |  | ||||||
|   'git commit -m "chore: root"' |  | ||||||
|   , {cwd: cwd}, function (err, stdout, stderr) { |  | ||||||
|     if (err) { |  | ||||||
|       console.log(stdout, stderr) |  | ||||||
|       return cb(err) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     cb(null, cwd) |  | ||||||
|   }) |  | ||||||
| } |  | ||||||
| @ -1,30 +0,0 @@ | |||||||
| var mockSpawn = require('mock-spawn')() |  | ||||||
| mockSpawn.setStrategy(function (command, args, opts) { |  | ||||||
|   return function (cb) { |  | ||||||
|     this.stdout.write( |  | ||||||
|       /\.\.HEAD/.test(args.join(' ')) |  | ||||||
|         ? rawCommits[0] |  | ||||||
|         : rawCommits.join() |  | ||||||
|     ) |  | ||||||
|     cb(0) |  | ||||||
|   } |  | ||||||
| }) |  | ||||||
| 
 |  | ||||||
| const rawCommits = [ |  | ||||||
|   'hash-one==SPLIT==commit-one==END==\n', |  | ||||||
|   'hash-two==SPLIT==commit-two==END==\n' |  | ||||||
| ] |  | ||||||
| 
 |  | ||||||
| module.exports = { |  | ||||||
|   exec: function (command, options, cb) { |  | ||||||
|     if (typeof cb === 'undefined' && typeof options === 'function') { |  | ||||||
|       cb = options |  | ||||||
|     } |  | ||||||
|     if (/contains/.test(command)) { |  | ||||||
|       if (/notinhistory/.test(command)) return cb(new Error()) |  | ||||||
|       return cb(null, 'whatever\nmaster\n') |  | ||||||
|     } |  | ||||||
|   }, |  | ||||||
|   spawn: mockSpawn, |  | ||||||
|   '@noCallThru': true |  | ||||||
| } |  | ||||||
| @ -1,3 +0,0 @@ | |||||||
| module.exports = function (cb) { |  | ||||||
|   cb(null, 'bar') |  | ||||||
| } |  | ||||||
| @ -1,17 +0,0 @@ | |||||||
| module.exports = function () { |  | ||||||
|   return { |  | ||||||
|     authenticate: function () { |  | ||||||
|       return true |  | ||||||
|     }, |  | ||||||
|     gitdata: { |  | ||||||
|       createReference: function (release, cb) { |  | ||||||
|         cb(null) |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     repos: { |  | ||||||
|       createRelease: function (release, cb) { |  | ||||||
|         cb(null) |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @ -1,3 +0,0 @@ | |||||||
| module.exports = function (config, options, cb) { |  | ||||||
|   cb(new Error('a')) |  | ||||||
| } |  | ||||||
| @ -1,3 +0,0 @@ | |||||||
| module.exports = function (config, options, cb) { |  | ||||||
|   cb(new Error('b')) |  | ||||||
| } |  | ||||||
| @ -1,3 +0,0 @@ | |||||||
| module.exports = function (config, options, cb) { |  | ||||||
|   cb(null, 'a') |  | ||||||
| } |  | ||||||
| @ -1,3 +0,0 @@ | |||||||
| module.exports = function (config, options, cb) { |  | ||||||
|   cb(null, 'b') |  | ||||||
| } |  | ||||||
| @ -1,28 +0,0 @@ | |||||||
| const nock = require('nock') |  | ||||||
| 
 |  | ||||||
| const availableModule = { |  | ||||||
|   'dist-tags': { |  | ||||||
|     latest: '1.33.7', |  | ||||||
|     foo: '0.8.15' |  | ||||||
|   }, |  | ||||||
|   versions: { |  | ||||||
|     '0.8.15': { |  | ||||||
|       gitHead: 'bar' |  | ||||||
|     }, |  | ||||||
|     '1.33.7': { |  | ||||||
|       gitHead: 'HEAD' |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| module.exports = nock('http://registry.npmjs.org') |  | ||||||
|   .get('/available') |  | ||||||
|   .reply(200, availableModule) |  | ||||||
|   .get('/tagged') |  | ||||||
|   .reply(200, availableModule) |  | ||||||
|   .get('/untagged') |  | ||||||
|   .reply(200, availableModule) |  | ||||||
|   .get('/@scoped%2Favailable') |  | ||||||
|   .reply(200, availableModule) |  | ||||||
|   .get('/unavailable') |  | ||||||
|   .reply(404, {}) |  | ||||||
							
								
								
									
										78
									
								
								test/plugins.test.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								test/plugins.test.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,78 @@ | |||||||
|  | import test from 'ava'; | ||||||
|  | import plugins from '../src/lib/plugins'; | ||||||
|  | 
 | ||||||
|  | test('Export plugins', t => { | ||||||
|  |   // Call the plugin module
 | ||||||
|  |   const defaultPlugins = plugins({}); | ||||||
|  | 
 | ||||||
|  |   // Verify the module returns a function for each plugin
 | ||||||
|  |   t.is(typeof defaultPlugins.analyzeCommits, 'function'); | ||||||
|  |   t.is(typeof defaultPlugins.generateNotes, 'function'); | ||||||
|  |   t.is(typeof defaultPlugins.verifyConditions, 'function'); | ||||||
|  |   t.is(typeof defaultPlugins.verifyRelease, 'function'); | ||||||
|  |   t.is(typeof defaultPlugins.getLastRelease, 'function'); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test('Pipeline - Get all results', async t => { | ||||||
|  |   // Call the plugin module with a verifyRelease plugin pipeline
 | ||||||
|  |   const pipelinePlugins = plugins({ | ||||||
|  |     verifyRelease: ['./src/lib/plugin-noop', './test/fixtures/plugin-result-a', './test/fixtures/plugin-result-b'], | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   // Call the verifyRelease pipeline
 | ||||||
|  |   const results = await pipelinePlugins.verifyRelease({}); | ||||||
|  | 
 | ||||||
|  |   // Verify the pipeline return the expected result for each plugin, in order
 | ||||||
|  |   t.deepEqual(results, [undefined, 'a', 'b']); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test('Pipeline - Pass pluginConfig and options to each plugins', async t => { | ||||||
|  |   // Plugin configuration with options (plugin-result-config is a mock plugin returning its pluginConfig and options parameters)
 | ||||||
|  |   const pluginConfig = {path: './test/fixtures/plugin-result-config', pluginParam: 'param1'}; | ||||||
|  |   // Semantic-release global options
 | ||||||
|  |   const options = {semanticReleaseParam: 'param2'}; | ||||||
|  |   // Call the plugin module with a verifyRelease plugin pipeline
 | ||||||
|  |   const pipelinePlugins = plugins({ | ||||||
|  |     verifyRelease: [pluginConfig, './test/fixtures/plugin-result-config'], | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   // Call the verifyRelease pipeline
 | ||||||
|  |   const results = await pipelinePlugins.verifyRelease(options); | ||||||
|  | 
 | ||||||
|  |   // Verify the pipeline first result is the pluginConfig and options parameters (to verify the plugin was called with the defined pluginConfig and options parameters)
 | ||||||
|  |   t.deepEqual(results, [{pluginConfig, options}, {pluginConfig: {}, options}]); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test('Pipeline - Get first error', async t => { | ||||||
|  |   // Call the plugin module with a verifyRelease plugin pipeline
 | ||||||
|  |   const pipelinePlugins = plugins({ | ||||||
|  |     verifyRelease: ['./src/lib/plugin-noop', './test/fixtures/plugin-error-a', './test/fixtures/plugin-error-b'], | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   // Call the verifyRelease pipeline and verify it returns the error thrown by './test/fixtures/plugin-error-a'
 | ||||||
|  |   await t.throws(pipelinePlugins.verifyRelease({}), 'a'); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test('Normalize and load plugin from string', t => { | ||||||
|  |   // Call the normalize function with a path
 | ||||||
|  |   const plugin = plugins.normalize('./src/lib/plugin-noop'); | ||||||
|  | 
 | ||||||
|  |   // Verify the plugin is loaded
 | ||||||
|  |   t.is(typeof plugin, 'function'); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test('Normalize and load plugin from object', t => { | ||||||
|  |   // Call the normalize function with an object (with path property)
 | ||||||
|  |   const plugin = plugins.normalize({path: './src/lib/plugin-noop'}); | ||||||
|  | 
 | ||||||
|  |   // Verify the plugin is loaded
 | ||||||
|  |   t.is(typeof plugin, 'function'); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test('load from fallback', t => { | ||||||
|  |   // Call the normalize function with a fallback
 | ||||||
|  |   const plugin = plugins.normalize(null, '../lib/plugin-noop'); | ||||||
|  | 
 | ||||||
|  |   // Verify the fallback plugin is loaded
 | ||||||
|  |   t.is(typeof plugin, 'function'); | ||||||
|  | }); | ||||||
							
								
								
									
										151
									
								
								test/post.test.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								test/post.test.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,151 @@ | |||||||
|  | import {callbackify} from 'util'; | ||||||
|  | import test from 'ava'; | ||||||
|  | import {gitRepo, gitCommits, gitHead} from './helpers/git-utils'; | ||||||
|  | import {stub} from 'sinon'; | ||||||
|  | import nock from 'nock'; | ||||||
|  | import {authenticate} from './helpers/mock-github'; | ||||||
|  | import post from '../src/post'; | ||||||
|  | 
 | ||||||
|  | test.beforeEach(t => { | ||||||
|  |   // Save the current working diretory
 | ||||||
|  |   t.context.cwd = process.cwd(); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test.afterEach.always(t => { | ||||||
|  |   // Restore the current working directory
 | ||||||
|  |   process.chdir(t.context.cwd); | ||||||
|  |   // Reset nock
 | ||||||
|  |   nock.cleanAll(); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test.serial('Post run with github token', async t => { | ||||||
|  |   // Create a git repository, set the current working directory at the root of the repo
 | ||||||
|  |   await gitRepo(); | ||||||
|  |   // Add commits to the master branch
 | ||||||
|  |   await gitCommits(['fix: First fix', 'feat: Second feature']); | ||||||
|  | 
 | ||||||
|  |   const sha = await gitHead(); | ||||||
|  |   const owner = 'test_user'; | ||||||
|  |   const repo = 'test_repo'; | ||||||
|  |   const githubUrl = 'https://testurl.com:443'; | ||||||
|  |   const githubToken = 'github_token'; | ||||||
|  |   const githubApiPathPrefix = 'prefix'; | ||||||
|  |   const releaseLog = 'Test release note body'; | ||||||
|  |   // Stub the generateNotes plugin
 | ||||||
|  |   const generateNotes = stub().resolves(releaseLog); | ||||||
|  |   const version = '1.0.0'; | ||||||
|  |   const branch = 'master'; | ||||||
|  |   const debug = false; | ||||||
|  |   const tagName = `v${version}`; | ||||||
|  |   const options = {branch, debug, githubUrl, githubToken, githubApiPathPrefix}; | ||||||
|  |   const pkg = {version, repository: {url: `git+https://othertesturl.com/${owner}/${repo}.git`}}; | ||||||
|  | 
 | ||||||
|  |   // Mock github API for releases and git/refs endpoints
 | ||||||
|  |   const github = authenticate({githubUrl, githubToken, githubApiPathPrefix}) | ||||||
|  |     .post(`/repos/${owner}/${repo}/releases`, { | ||||||
|  |       tag_name: tagName, | ||||||
|  |       target_commitish: branch, | ||||||
|  |       name: tagName, | ||||||
|  |       body: releaseLog, | ||||||
|  |       draft: debug, | ||||||
|  |     }) | ||||||
|  |     .reply({}) | ||||||
|  |     .post(`/repos/${owner}/${repo}/git/refs`, {ref: `refs/tags/${tagName}`, sha}) | ||||||
|  |     .reply({}); | ||||||
|  | 
 | ||||||
|  |   // Call the post module
 | ||||||
|  |   const result = await post({pkg, options, plugins: {generateNotes: callbackify(generateNotes)}}); | ||||||
|  | 
 | ||||||
|  |   // Verify the getLastRelease plugin has been called with 'options' and 'pkg'
 | ||||||
|  |   t.true(generateNotes.calledOnce); | ||||||
|  |   t.deepEqual(generateNotes.firstCall.args[0].options, options); | ||||||
|  |   t.deepEqual(generateNotes.firstCall.args[0].pkg, pkg); | ||||||
|  | 
 | ||||||
|  |   // Verify the published release note
 | ||||||
|  |   t.deepEqual(result, { | ||||||
|  |     published: true, | ||||||
|  |     release: {owner, repo, tag_name: tagName, name: tagName, target_commitish: branch, draft: debug, body: releaseLog}, | ||||||
|  |   }); | ||||||
|  |   // Verify the releases and git/refs endpoint have been call with expected requests
 | ||||||
|  |   t.true(github.isDone()); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test.serial('Post dry run with github token', async t => { | ||||||
|  |   // Create a git repository, set the current working directory at the root of the repo
 | ||||||
|  |   await gitRepo(); | ||||||
|  |   // Add commits to the master branch
 | ||||||
|  |   await gitCommits(['fix: First fix', 'feat: Second feature']); | ||||||
|  | 
 | ||||||
|  |   const owner = 'test_user'; | ||||||
|  |   const repo = 'test_repo'; | ||||||
|  |   const githubToken = 'github_token'; | ||||||
|  |   const releaseLog = 'Test release note body'; | ||||||
|  |   // Stub the generateNotes plugin
 | ||||||
|  |   const generateNotes = stub().resolves(releaseLog); | ||||||
|  |   const version = '1.0.0'; | ||||||
|  |   const branch = 'master'; | ||||||
|  |   const debug = true; | ||||||
|  |   const tagName = `v${version}`; | ||||||
|  |   const options = {branch, debug, githubToken}; | ||||||
|  |   const pkg = {version, repository: {url: `git+https://othertesturl.com/${owner}/${repo}.git`}}; | ||||||
|  | 
 | ||||||
|  |   // Mock github API for releases endpoint
 | ||||||
|  |   const github = authenticate({githubToken}) | ||||||
|  |     .post(`/repos/${owner}/${repo}/releases`, { | ||||||
|  |       tag_name: tagName, | ||||||
|  |       target_commitish: branch, | ||||||
|  |       name: tagName, | ||||||
|  |       body: releaseLog, | ||||||
|  |       draft: debug, | ||||||
|  |     }) | ||||||
|  |     .reply({}); | ||||||
|  | 
 | ||||||
|  |   // Call the post module
 | ||||||
|  |   const result = await post({pkg, options, plugins: {generateNotes: callbackify(generateNotes)}}); | ||||||
|  | 
 | ||||||
|  |   // Verify the getLastRelease plugin has been called with 'options' and 'pkg'
 | ||||||
|  |   t.true(generateNotes.calledOnce); | ||||||
|  |   t.deepEqual(generateNotes.firstCall.args[0].options, options); | ||||||
|  |   t.deepEqual(generateNotes.firstCall.args[0].pkg, pkg); | ||||||
|  | 
 | ||||||
|  |   // Verify the published release note
 | ||||||
|  |   t.deepEqual(result, { | ||||||
|  |     published: true, | ||||||
|  |     release: {owner, repo, tag_name: tagName, name: tagName, target_commitish: branch, draft: debug, body: releaseLog}, | ||||||
|  |   }); | ||||||
|  |   // Verify the releases and git/refs endpoint have been call with expected requests
 | ||||||
|  |   t.true(github.isDone()); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test.serial('Post dry run without github token', async t => { | ||||||
|  |   // Create a git repository, set the current working directory at the root of the repo
 | ||||||
|  |   await gitRepo(); | ||||||
|  |   // Add commits to the master branch
 | ||||||
|  |   await gitCommits(['fix: First fix', 'feat: Second feature']); | ||||||
|  | 
 | ||||||
|  |   const owner = 'test_user'; | ||||||
|  |   const repo = 'test_repo'; | ||||||
|  |   const releaseLog = 'Test release note body'; | ||||||
|  |   // Stub the generateNotes plugin
 | ||||||
|  |   const generateNotes = stub().resolves(releaseLog); | ||||||
|  |   const version = '1.0.0'; | ||||||
|  |   const branch = 'master'; | ||||||
|  |   const debug = true; | ||||||
|  |   const tagName = `v${version}`; | ||||||
|  |   const options = {branch, debug}; | ||||||
|  |   const pkg = {version, repository: {url: `git+https://othertesturl.com/${owner}/${repo}.git`}}; | ||||||
|  | 
 | ||||||
|  |   // Call the post module
 | ||||||
|  |   const result = await post({pkg, options, plugins: {generateNotes: callbackify(generateNotes)}}); | ||||||
|  | 
 | ||||||
|  |   // Verify the getLastRelease plugin has been called with 'options' and 'pkg'
 | ||||||
|  |   t.true(generateNotes.calledOnce); | ||||||
|  |   t.deepEqual(generateNotes.firstCall.args[0].options, options); | ||||||
|  |   t.deepEqual(generateNotes.firstCall.args[0].pkg, pkg); | ||||||
|  | 
 | ||||||
|  |   // Verify the release note
 | ||||||
|  |   t.deepEqual(result, { | ||||||
|  |     published: false, | ||||||
|  |     release: {owner, repo, tag_name: tagName, name: tagName, target_commitish: branch, draft: debug, body: releaseLog}, | ||||||
|  |   }); | ||||||
|  | }); | ||||||
							
								
								
									
										163
									
								
								test/pre.test.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										163
									
								
								test/pre.test.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,163 @@ | |||||||
|  | import {callbackify} from 'util'; | ||||||
|  | import test from 'ava'; | ||||||
|  | import {gitRepo, gitCommits} from './helpers/git-utils'; | ||||||
|  | import proxyquire from 'proxyquire'; | ||||||
|  | import {stub} from 'sinon'; | ||||||
|  | 
 | ||||||
|  | // Stub to capture the log messages
 | ||||||
|  | const errorLog = stub(); | ||||||
|  | // Module to test
 | ||||||
|  | const pre = proxyquire('../src/pre', { | ||||||
|  |   './lib/get-commits': proxyquire('../src/lib/get-commits', {npmlog: {error: errorLog}}), | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test.beforeEach(t => { | ||||||
|  |   // Save the current working diretory
 | ||||||
|  |   t.context.cwd = process.cwd(); | ||||||
|  |   // Reset the stub call history
 | ||||||
|  |   errorLog.resetHistory(); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test.afterEach.always(t => { | ||||||
|  |   // Restore the current working directory
 | ||||||
|  |   process.chdir(t.context.cwd); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test.serial('Increase version', async t => { | ||||||
|  |   // Create a git repository, set the current working directory at the root of the repo
 | ||||||
|  |   await gitRepo(); | ||||||
|  |   // Add commits to the master branch
 | ||||||
|  |   const cmts = await gitCommits(['fix: First fix', 'feat: Second feature']); | ||||||
|  | 
 | ||||||
|  |   const options = {branch: 'master'}; | ||||||
|  |   const pkg = {name: 'available'}; | ||||||
|  |   const lastRelease = {version: '1.0.0', gitHead: cmts[cmts.length - 1].hash}; | ||||||
|  |   // Stub the getLastRelease, analyzeCommits and verifyRelease plugins
 | ||||||
|  |   const getLastRelease = stub().resolves(lastRelease); | ||||||
|  |   const analyzeCommits = stub().resolves('major'); | ||||||
|  |   const verifyRelease = stub().resolves(); | ||||||
|  | 
 | ||||||
|  |   // Call the pre module
 | ||||||
|  |   const nextRelease = await pre({ | ||||||
|  |     options, | ||||||
|  |     pkg, | ||||||
|  |     plugins: { | ||||||
|  |       getLastRelease: callbackify(getLastRelease), | ||||||
|  |       analyzeCommits: callbackify(analyzeCommits), | ||||||
|  |       verifyRelease: callbackify(verifyRelease), | ||||||
|  |     }, | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   // Verify the pre module returns the 'type' returned by analyzeCommits and the 'version' returned by getLastRelease increamented with the 'type' (current version 1.0.0 => major release = version 2.0.0)
 | ||||||
|  |   t.deepEqual(nextRelease, {type: 'major', version: '2.0.0'}); | ||||||
|  | 
 | ||||||
|  |   // Verify the getLastRelease plugin has been called with 'options' and 'pkg'
 | ||||||
|  |   t.true(getLastRelease.calledOnce); | ||||||
|  |   t.deepEqual(getLastRelease.firstCall.args[0].options, options); | ||||||
|  |   t.deepEqual(getLastRelease.firstCall.args[0].pkg, pkg); | ||||||
|  | 
 | ||||||
|  |   // Verify the analyzeCommits plugin has been called with the repo 'commits' since lastVersion githead
 | ||||||
|  |   t.true(analyzeCommits.calledOnce); | ||||||
|  |   t.is(analyzeCommits.firstCall.args[0].commits.length, 1); | ||||||
|  |   t.is(analyzeCommits.firstCall.args[0].commits[0].hash.substring(0, 7), cmts[0].hash); | ||||||
|  |   t.is(analyzeCommits.firstCall.args[0].commits[0].message, cmts[0].message); | ||||||
|  | 
 | ||||||
|  |   // Verify the verifyRelease plugin has been called with 'lastRelease' and 'nextRelease'
 | ||||||
|  |   t.true(verifyRelease.calledOnce); | ||||||
|  |   t.deepEqual(verifyRelease.firstCall.args[0].lastRelease, lastRelease); | ||||||
|  |   t.deepEqual(verifyRelease.firstCall.args[0].nextRelease, nextRelease); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test.serial('Initial version', async t => { | ||||||
|  |   // Create a git repository, set the current working directory at the root of the repo
 | ||||||
|  |   await gitRepo(); | ||||||
|  |   // Add commits to the master branch
 | ||||||
|  |   const cmts = await gitCommits(['fix(scope1): First fix', 'feat(scope2): Second feature']); | ||||||
|  | 
 | ||||||
|  |   const options = {branch: 'master'}; | ||||||
|  |   const pkg = {name: 'available'}; | ||||||
|  |   const lastRelease = {version: null, gitHead: undefined}; | ||||||
|  |   // Stub the getLastRelease, analyzeCommits and verifyRelease plugins
 | ||||||
|  |   const getLastRelease = stub().resolves({version: null, gitHead: undefined}); | ||||||
|  |   const analyzeCommits = stub().resolves('major'); | ||||||
|  |   const verifyRelease = stub().resolves(); | ||||||
|  | 
 | ||||||
|  |   // Call the pre module
 | ||||||
|  |   const nextRelease = await pre({ | ||||||
|  |     options, | ||||||
|  |     pkg, | ||||||
|  |     plugins: { | ||||||
|  |       getLastRelease: callbackify(getLastRelease), | ||||||
|  |       analyzeCommits: callbackify(analyzeCommits), | ||||||
|  |       verifyRelease: callbackify(verifyRelease), | ||||||
|  |     }, | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   // Verify the pre module returns the 'type' returned by analyzeCommits and the 'version' returned by getLastRelease increamented with the 'type' (no current version => initial release = version 1.0.0)
 | ||||||
|  |   t.deepEqual(nextRelease, {type: 'initial', version: '1.0.0'}); | ||||||
|  | 
 | ||||||
|  |   // Verify the getLastRelease plugin has been called with 'options' and 'pkg'
 | ||||||
|  |   t.true(getLastRelease.calledOnce); | ||||||
|  |   t.deepEqual(getLastRelease.firstCall.args[0].options, options); | ||||||
|  |   t.deepEqual(getLastRelease.firstCall.args[0].pkg, pkg); | ||||||
|  | 
 | ||||||
|  |   // Verify the analyzeCommits plugin has been called with all the repo 'commits'
 | ||||||
|  |   t.true(analyzeCommits.calledOnce); | ||||||
|  |   t.is(analyzeCommits.firstCall.args[0].commits.length, 2); | ||||||
|  |   t.is(analyzeCommits.firstCall.args[0].commits[0].hash.substring(0, 7), cmts[0].hash); | ||||||
|  |   t.is(analyzeCommits.firstCall.args[0].commits[0].message, cmts[0].message); | ||||||
|  |   t.is(analyzeCommits.firstCall.args[0].commits[1].hash.substring(0, 7), cmts[1].hash); | ||||||
|  |   t.is(analyzeCommits.firstCall.args[0].commits[1].message, cmts[1].message); | ||||||
|  | 
 | ||||||
|  |   // Verify the verifyRelease plugin has been called with 'lastRelease' and 'nextRelease'
 | ||||||
|  |   t.true(verifyRelease.calledOnce); | ||||||
|  |   t.deepEqual(verifyRelease.firstCall.args[0].lastRelease, lastRelease); | ||||||
|  |   t.deepEqual(verifyRelease.firstCall.args[0].nextRelease, nextRelease); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test.serial('Throws error if verifyRelease fails', async t => { | ||||||
|  |   // Create a git repository, set the current working directory at the root of the repo
 | ||||||
|  |   await gitRepo(); | ||||||
|  |   // Add commits to the master branch
 | ||||||
|  |   const cmts = await gitCommits(['fix: First fix', 'feat: Second feature']); | ||||||
|  | 
 | ||||||
|  |   const options = {branch: 'master'}; | ||||||
|  |   const pkg = {name: 'available'}; | ||||||
|  |   const lastRelease = {version: '1.0.0', gitHead: cmts[cmts.length - 1].hash}; | ||||||
|  |   // Stub the getLastRelease, analyzeCommits and verifyRelease plugins
 | ||||||
|  |   const getLastRelease = stub().resolves(lastRelease); | ||||||
|  |   const analyzeCommits = stub().resolves('major'); | ||||||
|  |   const verifyRelease = stub().rejects(new Error('verifyRelease failed')); | ||||||
|  | 
 | ||||||
|  |   // Call the pre module and verify it returns the Error returned by verifyRelease
 | ||||||
|  |   const error = await t.throws( | ||||||
|  |     pre({ | ||||||
|  |       options, | ||||||
|  |       pkg, | ||||||
|  |       plugins: { | ||||||
|  |         getLastRelease: callbackify(getLastRelease), | ||||||
|  |         analyzeCommits: callbackify(analyzeCommits), | ||||||
|  |         verifyRelease: callbackify(verifyRelease), | ||||||
|  |       }, | ||||||
|  |     }) | ||||||
|  |   ); | ||||||
|  | 
 | ||||||
|  |   // Verify the error message is the one returned by verifyRelease
 | ||||||
|  |   t.is(error.message, 'verifyRelease failed'); | ||||||
|  | 
 | ||||||
|  |   // Verify the getLastRelease plugin has been called with 'options' and 'pkg'
 | ||||||
|  |   t.true(getLastRelease.calledOnce); | ||||||
|  |   t.deepEqual(getLastRelease.firstCall.args[0].options, options); | ||||||
|  |   t.deepEqual(getLastRelease.firstCall.args[0].pkg, pkg); | ||||||
|  | 
 | ||||||
|  |   // Verify the analyzeCommits plugin has been called with all the repo 'commits'
 | ||||||
|  |   t.true(analyzeCommits.calledOnce); | ||||||
|  |   t.is(analyzeCommits.firstCall.args[0].commits.length, 1); | ||||||
|  |   t.is(analyzeCommits.firstCall.args[0].commits[0].hash.substring(0, 7), cmts[0].hash); | ||||||
|  |   t.is(analyzeCommits.firstCall.args[0].commits[0].message, cmts[0].message); | ||||||
|  | 
 | ||||||
|  |   // Verify the verifyRelease plugin has been called with 'lastRelease' and 'nextRelease'
 | ||||||
|  |   t.true(verifyRelease.calledOnce); | ||||||
|  |   t.deepEqual(verifyRelease.firstCall.args[0].lastRelease, lastRelease); | ||||||
|  |   t.deepEqual(verifyRelease.firstCall.args[0].nextRelease, {type: 'major', version: '2.0.0'}); | ||||||
|  | }); | ||||||
| @ -1,11 +0,0 @@ | |||||||
| var exec = require('child_process').exec |  | ||||||
| 
 |  | ||||||
| var opts = { |  | ||||||
|   cwd: __dirname |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| module.exports = { |  | ||||||
|   start: exec.bind(null, './start.sh', opts), |  | ||||||
|   stop: exec.bind(null, './stop.sh', opts), |  | ||||||
|   uri: 'http://localhost:' + (process.env.TRAVIS === 'true' ? 5984 : 15986) + '/registry/_design/app/_rewrite/' |  | ||||||
| } |  | ||||||
| @ -1,96 +0,0 @@ | |||||||
| var join = require('path').join |  | ||||||
| 
 |  | ||||||
| var tap = require('tap') |  | ||||||
| var rimraf = require('rimraf') |  | ||||||
| 
 |  | ||||||
| var registry = require('../registry') |  | ||||||
| var testModule = require('../lib/test-module') |  | ||||||
| var baseScenario = require('../lib/base-scenario') |  | ||||||
| 
 |  | ||||||
| var tearDown = tap.tearDown |  | ||||||
| var test = tap.test |  | ||||||
| 
 |  | ||||||
| test('change version', {bail: process.env.TRAVIS === 'true'}, function (t) { |  | ||||||
|   t.plan(7) |  | ||||||
| 
 |  | ||||||
|   registry.start(function (err, stdout, stderr) { |  | ||||||
|     t.error(err, 'registry started') |  | ||||||
|     if (err) return t.end() |  | ||||||
| 
 |  | ||||||
|     testModule('change-version', registry.uri, function (err, cwd) { |  | ||||||
|       t.error(err, 'test-module created') |  | ||||||
|       if (err) return t.end() |  | ||||||
| 
 |  | ||||||
|       t.test('no version', function (tt) { |  | ||||||
|         tt.plan(1) |  | ||||||
| 
 |  | ||||||
|         baseScenario(cwd, registry.uri) |  | ||||||
|           .env('npm_config_loglevel', 'info') |  | ||||||
|           .run('node ../../../bin/semantic-release.js pre') |  | ||||||
|           .stderr(/ENOCHANGE/) |  | ||||||
|           .code(1) |  | ||||||
|           .end(tt.error) |  | ||||||
|       }) |  | ||||||
| 
 |  | ||||||
|       t.test('initial version', function (tt) { |  | ||||||
|         tt.plan(1) |  | ||||||
| 
 |  | ||||||
|         baseScenario(cwd, registry.uri) |  | ||||||
|           .exec('git commit -m "feat: initial" --allow-empty') |  | ||||||
|           .exec('node ../../../bin/semantic-release.js pre') |  | ||||||
|           .run('npm publish') |  | ||||||
|           .stdout(/1\.0\.0/) |  | ||||||
|           .code(0) |  | ||||||
|           .end(tt.error) |  | ||||||
|       }) |  | ||||||
| 
 |  | ||||||
|       t.test('patch version', function (tt) { |  | ||||||
|         tt.plan(1) |  | ||||||
| 
 |  | ||||||
|         baseScenario(cwd, registry.uri) |  | ||||||
|           .exec('git commit -m "fix: foo" --allow-empty') |  | ||||||
|           .exec('node ../../../bin/semantic-release.js pre') |  | ||||||
|           .run('npm publish') |  | ||||||
|           .stdout(/1\.0\.1/) |  | ||||||
|           .code(0) |  | ||||||
|           .end(tt.error) |  | ||||||
|       }) |  | ||||||
| 
 |  | ||||||
|       t.test('feature version', function (tt) { |  | ||||||
|         tt.plan(1) |  | ||||||
| 
 |  | ||||||
|         baseScenario(cwd, registry.uri) |  | ||||||
|           .exec('git commit -m "feat: foo" --allow-empty') |  | ||||||
|           .exec('node ../../../bin/semantic-release.js pre') |  | ||||||
|           .run('npm publish') |  | ||||||
|           .code(0) |  | ||||||
|           .stdout(/1\.1\.0/) |  | ||||||
|           .end(tt.error) |  | ||||||
|       }) |  | ||||||
| 
 |  | ||||||
|       t.test('breaking version', function (tt) { |  | ||||||
|         tt.plan(1) |  | ||||||
| 
 |  | ||||||
|         baseScenario(cwd, registry.uri) |  | ||||||
|           .exec('git commit -m "feat: foo\n\n BREAKING CHANGE: bar" --allow-empty') |  | ||||||
|           .exec('node ../../../bin/semantic-release.js pre') |  | ||||||
|           .run('npm publish') |  | ||||||
|           .code(0) |  | ||||||
|           .stdout(/2\.0\.0/) |  | ||||||
|           .end(tt.error) |  | ||||||
|       }) |  | ||||||
|     }) |  | ||||||
|   }) |  | ||||||
| }) |  | ||||||
| 
 |  | ||||||
| tearDown(function () { |  | ||||||
|   if (process.env.TRAVIS === 'true') return |  | ||||||
| 
 |  | ||||||
|   function cb (err, stdout, stderr) { |  | ||||||
|     if (err) console.log(err) |  | ||||||
|     if (stderr) console.log(stderr) |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   rimraf(join(__dirname, '../tmp'), cb) |  | ||||||
|   registry.stop(cb) |  | ||||||
| }) |  | ||||||
| @ -1,43 +0,0 @@ | |||||||
| var test = require('tap').test |  | ||||||
| var proxyquire = require('proxyquire') |  | ||||||
| 
 |  | ||||||
| var commits = proxyquire('../../src/lib/commits', { |  | ||||||
|   'npmlog': { |  | ||||||
|     error: function () {} |  | ||||||
|   }, |  | ||||||
|   'child_process': require('../mocks/child-process') |  | ||||||
| }) |  | ||||||
| 
 |  | ||||||
| test('commits since last release', function (t) { |  | ||||||
|   t.test('get all commits', function (tt) { |  | ||||||
|     commits({lastRelease: {}, options: {branch: 'master'}}, function (err, commits) { |  | ||||||
|       tt.error(err) |  | ||||||
|       tt.is(commits.length, 2, 'all commits') |  | ||||||
|       tt.is(commits[0].hash, 'hash-one', 'parsed hash') |  | ||||||
|       tt.is(commits[1].message, 'commit-two', 'parsed message') |  | ||||||
| 
 |  | ||||||
|       tt.end() |  | ||||||
|     }) |  | ||||||
|   }) |  | ||||||
| 
 |  | ||||||
|   t.test('get commits since hash', function (tt) { |  | ||||||
|     commits({lastRelease: {gitHead: 'hash'}, options: {branch: 'master'}}, function (err, commits) { |  | ||||||
|       tt.error(err) |  | ||||||
|       tt.is(commits.length, 1, 'specified commits') |  | ||||||
|       tt.is(commits[0].hash, 'hash-one', 'parsed hash') |  | ||||||
|       tt.is(commits[0].message, 'commit-one', 'parsed message') |  | ||||||
| 
 |  | ||||||
|       tt.end() |  | ||||||
|     }) |  | ||||||
|   }) |  | ||||||
| 
 |  | ||||||
|   t.test('get commits since hash', function (tt) { |  | ||||||
|     commits({lastRelease: {gitHead: 'notinhistory'}, options: {branch: 'notmaster'}}, function (err, commits) { |  | ||||||
|       tt.ok(err) |  | ||||||
|       tt.is(err.code, 'ENOTINHISTORY') |  | ||||||
|       tt.end() |  | ||||||
|     }) |  | ||||||
|   }) |  | ||||||
| 
 |  | ||||||
|   t.end() |  | ||||||
| }) |  | ||||||
| @ -1,44 +0,0 @@ | |||||||
| const test = require('tap').test |  | ||||||
| 
 |  | ||||||
| const getRegistry = require('../../src/lib/get-registry') |  | ||||||
| 
 |  | ||||||
| test('get correct registry', function (t) { |  | ||||||
|   t.is(getRegistry({ |  | ||||||
|     name: 'publish-config', |  | ||||||
|     publishConfig: { |  | ||||||
|       registry: 'a' |  | ||||||
|     }}, |  | ||||||
|   {}), 'a') |  | ||||||
| 
 |  | ||||||
|   t.is(getRegistry({name: 'normal'}, { |  | ||||||
|     get: function () { |  | ||||||
|       return 'b' |  | ||||||
|     } |  | ||||||
|   }), 'b') |  | ||||||
| 
 |  | ||||||
|   t.is(getRegistry({name: 'normal'}, { |  | ||||||
|     get: function () { |  | ||||||
|       return null |  | ||||||
|     } |  | ||||||
|   }), 'https://registry.npmjs.org/') |  | ||||||
| 
 |  | ||||||
|   t.is(getRegistry({name: '@scoped/foo'}, { |  | ||||||
|     get: function (input) { |  | ||||||
|       return input === '@scoped/registry' ? 'c' : 'd' |  | ||||||
|     } |  | ||||||
|   }), 'c') |  | ||||||
| 
 |  | ||||||
|   t.is(getRegistry({name: '@scoped/bar'}, { |  | ||||||
|     get: function () { |  | ||||||
|       return 'e' |  | ||||||
|     } |  | ||||||
|   }), 'e') |  | ||||||
| 
 |  | ||||||
|   t.is(getRegistry({name: '@scoped/baz'}, { |  | ||||||
|     get: function () { |  | ||||||
|       return null |  | ||||||
|     } |  | ||||||
|   }), 'https://registry.npmjs.org/') |  | ||||||
| 
 |  | ||||||
|   t.end() |  | ||||||
| }) |  | ||||||
| @ -1,97 +0,0 @@ | |||||||
| var test = require('tap').test |  | ||||||
| 
 |  | ||||||
| var plugins = require('../../src/lib/plugins') |  | ||||||
| 
 |  | ||||||
| test('export plugins', function (t) { |  | ||||||
|   t.plan(5) |  | ||||||
| 
 |  | ||||||
|   var defaultPlugins = plugins({}) |  | ||||||
| 
 |  | ||||||
|   t.is(typeof defaultPlugins.analyzeCommits, 'function') |  | ||||||
|   t.is(typeof defaultPlugins.generateNotes, 'function') |  | ||||||
|   t.is(typeof defaultPlugins.verifyConditions, 'function') |  | ||||||
|   t.is(typeof defaultPlugins.verifyRelease, 'function') |  | ||||||
|   t.is(typeof defaultPlugins.getLastRelease, 'function') |  | ||||||
| }) |  | ||||||
| 
 |  | ||||||
| test('plugin pipelines', function (t) { |  | ||||||
|   t.plan(3) |  | ||||||
| 
 |  | ||||||
|   t.test('get all results', function (tt) { |  | ||||||
|     var pipelinePlugins = plugins({ |  | ||||||
|       verifyRelease: [ |  | ||||||
|         './src/lib/plugin-noop', |  | ||||||
|         './test/mocks/plugin-result-a', |  | ||||||
|         './test/mocks/plugin-result-b' |  | ||||||
|       ] |  | ||||||
|     }) |  | ||||||
| 
 |  | ||||||
|     pipelinePlugins.verifyRelease({}, function (err, results) { |  | ||||||
|       tt.error(err) |  | ||||||
|       tt.same(results, [undefined, 'a', 'b']) |  | ||||||
|       tt.end() |  | ||||||
|     }) |  | ||||||
|   }) |  | ||||||
| 
 |  | ||||||
|   t.test('get first error', function (tt) { |  | ||||||
|     var pipelinePlugins = plugins({ |  | ||||||
|       verifyConditions: [ |  | ||||||
|         './src/lib/plugin-noop', |  | ||||||
|         './test/mocks/plugin-error-a', |  | ||||||
|         './test/mocks/plugin-error-b' |  | ||||||
|       ] |  | ||||||
|     }) |  | ||||||
| 
 |  | ||||||
|     pipelinePlugins.verifyConditions({}, function (err) { |  | ||||||
|       tt.is(err.message, 'a') |  | ||||||
|       tt.end() |  | ||||||
|     }) |  | ||||||
|   }) |  | ||||||
| 
 |  | ||||||
|   t.test('get error and only results before', function (tt) { |  | ||||||
|     var pipelinePlugins = plugins({ |  | ||||||
|       verifyRelease: [ |  | ||||||
|         './src/lib/plugin-noop', |  | ||||||
|         './test/mocks/plugin-result-a', |  | ||||||
|         './test/mocks/plugin-error-b', |  | ||||||
|         './test/mocks/plugin-result-b' |  | ||||||
|       ] |  | ||||||
|     }) |  | ||||||
| 
 |  | ||||||
|     pipelinePlugins.verifyRelease({}, function (err, results) { |  | ||||||
|       tt.is(err.message, 'b') |  | ||||||
|       tt.same(results, [undefined, 'a', undefined]) |  | ||||||
|       tt.end() |  | ||||||
|     }) |  | ||||||
|   }) |  | ||||||
| }) |  | ||||||
| 
 |  | ||||||
| test('normalize and load plugin', function (t) { |  | ||||||
|   t.test('load from string', function (tt) { |  | ||||||
|     var plugin = plugins.normalize('./src/lib/plugin-noop') |  | ||||||
| 
 |  | ||||||
|     tt.is(typeof plugin, 'function') |  | ||||||
| 
 |  | ||||||
|     tt.end() |  | ||||||
|   }) |  | ||||||
| 
 |  | ||||||
|   t.test('load from object', function (tt) { |  | ||||||
|     var plugin = plugins.normalize({ |  | ||||||
|       path: './src/lib/plugin-noop' |  | ||||||
|     }) |  | ||||||
| 
 |  | ||||||
|     tt.is(typeof plugin, 'function') |  | ||||||
| 
 |  | ||||||
|     tt.end() |  | ||||||
|   }) |  | ||||||
| 
 |  | ||||||
|   t.test('load from fallback', function (tt) { |  | ||||||
|     var plugin = plugins.normalize(null, '../../src/lib/plugin-noop') |  | ||||||
| 
 |  | ||||||
|     tt.is(typeof plugin, 'function') |  | ||||||
| 
 |  | ||||||
|     tt.end() |  | ||||||
|   }) |  | ||||||
| 
 |  | ||||||
|   t.end() |  | ||||||
| }) |  | ||||||
| @ -1,74 +0,0 @@ | |||||||
| var defaults = require('lodash').defaults |  | ||||||
| var test = require('tap').test |  | ||||||
| var proxyquire = require('proxyquire') |  | ||||||
| 
 |  | ||||||
| var post = proxyquire('../../src/post', { |  | ||||||
|   'git-head': require('../mocks/git-head'), |  | ||||||
|   github: require('../mocks/github') |  | ||||||
| }) |  | ||||||
| 
 |  | ||||||
| var pkg = { |  | ||||||
|   version: '1.0.0', |  | ||||||
|   repository: {url: 'http://github.com/whats/up.git'} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var plugins = { |  | ||||||
|   generateNotes: function (pkg, cb) { |  | ||||||
|     cb(null, 'the log') |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var defaultRelease = { |  | ||||||
|   owner: 'whats', |  | ||||||
|   repo: 'up', |  | ||||||
|   name: 'v1.0.0', |  | ||||||
|   tag_name: 'v1.0.0', |  | ||||||
|   target_commitish: 'master', |  | ||||||
|   body: 'the log' |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| test('full post run', function (t) { |  | ||||||
|   t.test('in debug mode w/o token', function (tt) { |  | ||||||
|     post({ |  | ||||||
|       options: {debug: true, branch: 'master'}, |  | ||||||
|       pkg: pkg, |  | ||||||
|       plugins: plugins |  | ||||||
|     }, function (err, published, release) { |  | ||||||
|       tt.error(err) |  | ||||||
|       tt.is(published, false) |  | ||||||
|       tt.match(release, defaults({draft: true}, defaultRelease)) |  | ||||||
| 
 |  | ||||||
|       tt.end() |  | ||||||
|     }) |  | ||||||
|   }) |  | ||||||
| 
 |  | ||||||
|   t.test('in debug mode w/token', function (tt) { |  | ||||||
|     post({ |  | ||||||
|       options: {debug: true, githubToken: 'yo', branch: 'master'}, |  | ||||||
|       pkg: pkg, |  | ||||||
|       plugins: plugins |  | ||||||
|     }, function (err, published, release) { |  | ||||||
|       tt.error(err) |  | ||||||
|       tt.is(published, true) |  | ||||||
|       tt.match(release, defaults({draft: true}, defaultRelease)) |  | ||||||
| 
 |  | ||||||
|       tt.end() |  | ||||||
|     }) |  | ||||||
|   }) |  | ||||||
| 
 |  | ||||||
|   t.test('production', function (tt) { |  | ||||||
|     post({ |  | ||||||
|       options: {githubToken: 'yo', branch: 'master'}, |  | ||||||
|       pkg: pkg, |  | ||||||
|       plugins: plugins |  | ||||||
|     }, function (err, published, release) { |  | ||||||
|       tt.error(err) |  | ||||||
|       tt.is(published, true) |  | ||||||
|       tt.match(release, defaultRelease) |  | ||||||
| 
 |  | ||||||
|       tt.end() |  | ||||||
|     }) |  | ||||||
|   }) |  | ||||||
| 
 |  | ||||||
|   t.end() |  | ||||||
| }) |  | ||||||
| @ -1,64 +0,0 @@ | |||||||
| var test = require('tap').test |  | ||||||
| var proxyquire = require('proxyquire') |  | ||||||
| 
 |  | ||||||
| require('../mocks/registry') |  | ||||||
| var pre = proxyquire('../../src/pre', { |  | ||||||
|   './lib/commits': proxyquire('../../src/lib/commits', { |  | ||||||
|     'child_process': require('../mocks/child-process') |  | ||||||
|   }) |  | ||||||
| }) |  | ||||||
| 
 |  | ||||||
| var versions = { |  | ||||||
|   available: '1.0.0' |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var plugins = { |  | ||||||
|   verifyRelease: function (release, cb) { |  | ||||||
|     cb(null, release) |  | ||||||
|   }, |  | ||||||
|   analyzeCommits: function (commits, cb) { |  | ||||||
|     cb(null, 'major') |  | ||||||
|   }, |  | ||||||
|   getLastRelease: function (config, cb) { |  | ||||||
|     cb(null, {version: versions[config.pkg.name] || null, gitHead: 'HEAD'}) |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var npm = { |  | ||||||
|   registry: 'http://registry.npmjs.org/', |  | ||||||
|   tag: 'latest' |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| test('full pre run', function (t) { |  | ||||||
|   t.test('increase version', function (tt) { |  | ||||||
|     tt.plan(3) |  | ||||||
| 
 |  | ||||||
|     pre({ |  | ||||||
|       options: {branch: 'master'}, |  | ||||||
|       npm: npm, |  | ||||||
|       pkg: {name: 'available'}, |  | ||||||
|       plugins: plugins |  | ||||||
|     }, function (err, release) { |  | ||||||
|       tt.error(err) |  | ||||||
|       tt.is(release.type, 'major') |  | ||||||
|       tt.is(release.version, '2.0.0') |  | ||||||
|     }) |  | ||||||
|   }) |  | ||||||
| 
 |  | ||||||
|   t.test('increase version', function (tt) { |  | ||||||
|     tt.plan(3) |  | ||||||
| 
 |  | ||||||
|     pre({ |  | ||||||
|       options: {branch: 'master'}, |  | ||||||
|       npm: npm, |  | ||||||
|       pkg: {name: 'unavailable'}, |  | ||||||
|       plugins: plugins |  | ||||||
|     }, function (err, release) { |  | ||||||
|       tt.error(err) |  | ||||||
|       tt.is(release.type, 'initial') |  | ||||||
|       tt.is(release.version, '1.0.0') |  | ||||||
|     }) |  | ||||||
|   }) |  | ||||||
| 
 |  | ||||||
|   t.end() |  | ||||||
| }) |  | ||||||
| @ -1,60 +0,0 @@ | |||||||
| var test = require('tap').test |  | ||||||
| 
 |  | ||||||
| var type = require('../../src/lib/type') |  | ||||||
| 
 |  | ||||||
| test('get type from commits', function (t) { |  | ||||||
|   t.test('get type from plugin', function (tt) { |  | ||||||
|     tt.plan(2) |  | ||||||
| 
 |  | ||||||
|     type({ |  | ||||||
|       commits: [{ |  | ||||||
|         hash: '0', |  | ||||||
|         message: 'a' |  | ||||||
|       }], |  | ||||||
|       lastRelease: {version: '1.0.0'}, |  | ||||||
|       plugins: { |  | ||||||
|         analyzeCommits: function (config, cb) { |  | ||||||
|           cb(null, 'major') |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|     }, function (err, type) { |  | ||||||
|       tt.error(err) |  | ||||||
|       tt.is(type, 'major') |  | ||||||
|     }) |  | ||||||
|   }) |  | ||||||
| 
 |  | ||||||
|   t.test('error when no changes', function (tt) { |  | ||||||
|     tt.plan(1) |  | ||||||
| 
 |  | ||||||
|     type({ |  | ||||||
|       commits: [], |  | ||||||
|       lastRelease: {}, |  | ||||||
|       plugins: { |  | ||||||
|         analyzeCommits: function (config, cb) { |  | ||||||
|           cb(null, null) |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|     }, function (err) { |  | ||||||
|       tt.is(err.code, 'ENOCHANGE') |  | ||||||
|     }) |  | ||||||
|   }) |  | ||||||
| 
 |  | ||||||
|   t.test('initial version', function (tt) { |  | ||||||
|     tt.plan(2) |  | ||||||
| 
 |  | ||||||
|     type({ |  | ||||||
|       commits: [], |  | ||||||
|       lastRelease: {}, |  | ||||||
|       plugins: { |  | ||||||
|         analyzeCommits: function (config, cb) { |  | ||||||
|           cb(null, 'major') |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|     }, function (err, type) { |  | ||||||
|       tt.error(err) |  | ||||||
|       tt.is(type, 'initial') |  | ||||||
|     }) |  | ||||||
|   }) |  | ||||||
| 
 |  | ||||||
|   t.end() |  | ||||||
| }) |  | ||||||
| @ -1,72 +0,0 @@ | |||||||
| var test = require('tap').test |  | ||||||
| 
 |  | ||||||
| var verify = require('../../src/lib/verify') |  | ||||||
| 
 |  | ||||||
| test('verify pkg, options and env', function (t) { |  | ||||||
|   t.test('dry run verification', function (tt) { |  | ||||||
|     var noErrors = verify({ |  | ||||||
|       options: {debug: true}, |  | ||||||
|       pkg: { |  | ||||||
|         name: 'package', |  | ||||||
|         repository: { |  | ||||||
|           url: 'http://github.com/whats/up.git' |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|     }) |  | ||||||
| 
 |  | ||||||
|     tt.is(noErrors.length, 0) |  | ||||||
| 
 |  | ||||||
|     var errors = verify({ |  | ||||||
|       options: {debug: true}, |  | ||||||
|       pkg: {} |  | ||||||
|     }) |  | ||||||
| 
 |  | ||||||
|     tt.is(errors.length, 2) |  | ||||||
|     tt.is(errors[0].code, 'ENOPKGNAME') |  | ||||||
|     tt.is(errors[1].code, 'ENOPKGREPO') |  | ||||||
| 
 |  | ||||||
|     tt.end() |  | ||||||
|   }) |  | ||||||
| 
 |  | ||||||
|   t.test('dry run verification for gitlab repo', function (tt) { |  | ||||||
|     var noErrors = verify({ |  | ||||||
|       options: {debug: true}, |  | ||||||
|       pkg: { |  | ||||||
|         name: 'package', |  | ||||||
|         repository: { |  | ||||||
|           url: 'http://gitlab.corp.com/whats/up.git' |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|     }) |  | ||||||
| 
 |  | ||||||
|     tt.is(noErrors.length, 0) |  | ||||||
|     tt.end() |  | ||||||
|   }) |  | ||||||
| 
 |  | ||||||
|   t.test('publish verification', function (tt) { |  | ||||||
|     var noErrors = verify({ |  | ||||||
|       env: {NPM_TOKEN: 'yo'}, |  | ||||||
|       options: {githubToken: 'sup'}, |  | ||||||
|       pkg: { |  | ||||||
|         name: 'package', |  | ||||||
|         repository: { |  | ||||||
|           url: 'http://github.com/whats/up.git' |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|     }) |  | ||||||
| 
 |  | ||||||
|     tt.is(noErrors.length, 0) |  | ||||||
| 
 |  | ||||||
|     var errors = verify({env: {}, options: {}, pkg: {}}) |  | ||||||
| 
 |  | ||||||
|     tt.is(errors.length, 4) |  | ||||||
|     tt.is(errors[0].code, 'ENOPKGNAME') |  | ||||||
|     tt.is(errors[1].code, 'ENOPKGREPO') |  | ||||||
|     tt.is(errors[2].code, 'ENOGHTOKEN') |  | ||||||
|     tt.is(errors[3].code, 'ENONPMTOKEN') |  | ||||||
| 
 |  | ||||||
|     tt.end() |  | ||||||
|   }) |  | ||||||
| 
 |  | ||||||
|   t.end() |  | ||||||
| }) |  | ||||||
							
								
								
									
										71
									
								
								test/verify.test.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								test/verify.test.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,71 @@ | |||||||
|  | import test from 'ava'; | ||||||
|  | import verify from '../src/lib/verify'; | ||||||
|  | 
 | ||||||
|  | test('Dry run - Verify pkg, options and env', t => { | ||||||
|  |   // Call the verify module with debug (Dry run), package name and repo URL
 | ||||||
|  |   const errors = verify({ | ||||||
|  |     options: {debug: true}, | ||||||
|  |     pkg: {name: 'package', repository: {url: 'http://github.com/whats/up.git'}}, | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   // Verify no error has been returned
 | ||||||
|  |   t.is(errors.length, 0); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test('Dry run - Returns errors for missing package name and repo', t => { | ||||||
|  |   // Call the verify module with debug (Dry run), no package name and no repo URL
 | ||||||
|  |   const errors = verify({options: {debug: true}, pkg: {}}); | ||||||
|  | 
 | ||||||
|  |   // Verify the module return an error for each missing configuration
 | ||||||
|  |   t.is(errors.length, 2); | ||||||
|  |   t.is(errors[0].code, 'ENOPKGNAME'); | ||||||
|  |   t.is(errors[1].code, 'ENOPKGREPO'); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test('Dry run - Verify pkg, options and env for gitlab repo', t => { | ||||||
|  |   // Call the verify module with debug (Dry run), no package name and no repo URL
 | ||||||
|  |   const errors = verify({ | ||||||
|  |     options: {debug: true}, | ||||||
|  |     pkg: {name: 'package', repository: {url: 'http://gitlab.corp.com/whats/up.git'}}, | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   // Verify no error has been returned
 | ||||||
|  |   t.is(errors.length, 0); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test('Publish - Verify pkg, options and env', t => { | ||||||
|  |   // Call the verify module with package name, repo URL, npm token and github token
 | ||||||
|  |   const errors = verify({ | ||||||
|  |     env: {NPM_TOKEN: 'yo'}, | ||||||
|  |     options: {githubToken: 'sup'}, | ||||||
|  |     pkg: {name: 'package', repository: {url: 'http://github.com/whats/up.git'}}, | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   // Verify no error has been returned
 | ||||||
|  |   t.is(errors.length, 0); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test('Publish - Returns errors for missing package name, repo github token and npm token', t => { | ||||||
|  |   // Call the verify module with no package name, no repo URL, no NPM token and no github token
 | ||||||
|  |   const errors = verify({env: {}, options: {}, pkg: {}}); | ||||||
|  | 
 | ||||||
|  |   // Verify the module return an error for each missing configuration
 | ||||||
|  |   t.is(errors.length, 4); | ||||||
|  |   t.is(errors[0].code, 'ENOPKGNAME'); | ||||||
|  |   t.is(errors[1].code, 'ENOPKGREPO'); | ||||||
|  |   t.is(errors[2].code, 'ENOGHTOKEN'); | ||||||
|  |   t.is(errors[3].code, 'ENONPMTOKEN'); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test('Publish - Returns errors for missing email when using legacy npm token', t => { | ||||||
|  |   // Call the verify module with package name, repo URL, old NPM token and github token and no npm email
 | ||||||
|  |   const errors = verify({ | ||||||
|  |     env: {NPM_OLD_TOKEN: 'yo'}, | ||||||
|  |     options: {githubToken: 'sup'}, | ||||||
|  |     pkg: {name: 'package', repository: {url: 'http://github.com/whats/up.git'}}, | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   // Verify the module return an error for each missing configuration
 | ||||||
|  |   t.is(errors.length, 1); | ||||||
|  |   t.is(errors[0].code, 'ENONPMTOKEN'); | ||||||
|  | }); | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user