├── .dont-break.json ├── .gitignore ├── .npmrc ├── .travis.yml ├── README.md ├── __snapshots__ └── next-ver-spec.js ├── bin └── next-ver.js ├── package.json └── src ├── compute-next-version.js ├── increment.js ├── index.js ├── next-ver-spec.js └── parse-commit.js /.dont-break.json: -------------------------------------------------------------------------------- 1 | [ 2 | "git@github.com:bahmutov/next-ver-test.git" 3 | ] 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=http://registry.npmjs.org/ 2 | save-exact=true 3 | progress=false 4 | 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | cache: 3 | directories: 4 | - node_modules 5 | notifications: 6 | email: true 7 | node_js: 8 | - '8' 9 | before_script: 10 | - npm prune 11 | after_success: 12 | - npm run semantic-release 13 | branches: 14 | except: 15 | - /^v\d+\.\d+\.\d+$/ 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # next-ver 2 | 3 | > Tells you the next semantic version for your local package 4 | 5 | [![NPM][npm-icon] ][npm-url] 6 | 7 | [![Build status][ci-image] ][ci-url] 8 | [![semantic-release][semantic-image] ][semantic-url] 9 | [![js-standard-style][standard-image]][standard-url] 10 | 11 | ## Install and use 12 | 13 | Imagine a local package with current version 2.3.0 and a single semantic 14 | commit `fix(something): fix the login`. This tools computes the next 15 | version that should be used. 16 | 17 | ```sh 18 | npm install -g next-ver 19 | next-ver 20 | next version should be 2.3.1 21 | ``` 22 | 23 | Then you can publish, for example using 24 | [npm version](https://docs.npmjs.com/cli/version) command or one of the 25 | good helper CLI tools, 26 | like [publish-please](https://github.com/inikulin/publish-please), [np](https://github.com/sindresorhus/np) 27 | or [ci-publish](https://github.com/bahmutov/ci-publish#readme). 28 | 29 | ```sh 30 | npm version 2.3.1 31 | ``` 32 | 33 | ## Incrementing version in package file 34 | 35 | If there is a new version, you can update the version in the "package.json" 36 | file and commit the change, including creating a tag, just like the 37 | command `npm version ` does. 38 | 39 | Just add `--go` to the `next-ver` command 40 | 41 | ```sh 42 | next-ver --go 43 | ``` 44 | 45 | ## Semantic version rules 46 | 47 | A semantic version has form "major.minor.patch" 48 | 49 | A typical semantic commit has message of the form "type(scope): message". 50 | Commits that do not follow this format are ignored when computing next version. 51 | 52 | If there is a commit `fix(something): ...` then new version should increment 53 | the "patch" number. A commit `feat(something): ...` will increment the 54 | "minor" number. Finally, a commit with text "BREAKING" anywhere in the message 55 | or the message body text will increment the "major" number. 56 | 57 | When there are multiple numbers, the will be only the highest single digit 58 | increment. For example, these 4 commits will increment the "minor" number 59 | only. 60 | 61 | ``` 62 | fix(this): ... 63 | fix(that): ... 64 | feat(log): ... 65 | feat(server): ... 66 | ``` 67 | 68 | ## CLI options 69 | 70 | - `--go` increments the version in the current folder's `package.json` 71 | - `--version, -v` shows the version of `next-ver` itself 72 | 73 | ## Related 74 | 75 | I recommend enforcing commit message format using 76 | [pre-git](https://github.com/bahmutov/pre-git) with its default 77 | [simple-commit-message](https://github.com/bahmutov/simple-commit-message#readme) 78 | format validator. They work great with 79 | [semantic-release](https://github.com/semantic-release/semantic-release) tool. 80 | 81 | * [latest-version-or-tag](https://github.com/bahmutov/latest-version-or-tag) is 82 | used to fetch NPM tags and compare latest to the version in `package.json` 83 | 84 | ## Testing and development 85 | 86 | Uses separate repo [next-ver-test](https://github.com/bahmutov/next-ver-test) 87 | for testing this tool via [dont-break](https://github.com/bahmutov/dont-break) 88 | 89 | ### Small print 90 | 91 | Author: Gleb Bahmutov <gleb.bahmutov@gmail.com> © 2016 92 | 93 | 94 | * [@bahmutov](https://twitter.com/bahmutov) 95 | * [glebbahmutov.com](https://glebbahmutov.com) 96 | * [blog](https://glebbahmutov.com/blog) 97 | 98 | 99 | License: MIT - do anything with the code, but don't blame me if it does not work. 100 | 101 | Support: if you find any problems with this module, email / tweet / 102 | [open issue](https://github.com/bahmutov/next-ver/issues) on Github 103 | 104 | ## MIT License 105 | 106 | Copyright (c) 2016 Gleb Bahmutov <gleb.bahmutov@gmail.com> 107 | 108 | Permission is hereby granted, free of charge, to any person 109 | obtaining a copy of this software and associated documentation 110 | files (the "Software"), to deal in the Software without 111 | restriction, including without limitation the rights to use, 112 | copy, modify, merge, publish, distribute, sublicense, and/or sell 113 | copies of the Software, and to permit persons to whom the 114 | Software is furnished to do so, subject to the following 115 | conditions: 116 | 117 | The above copyright notice and this permission notice shall be 118 | included in all copies or substantial portions of the Software. 119 | 120 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 121 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 122 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 123 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 124 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 125 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 126 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 127 | OTHER DEALINGS IN THE SOFTWARE. 128 | 129 | [npm-icon]: https://nodei.co/npm/next-ver.svg?downloads=true 130 | [npm-url]: https://npmjs.org/package/next-ver 131 | [ci-image]: https://travis-ci.org/bahmutov/next-ver.svg?branch=master 132 | [ci-url]: https://travis-ci.org/bahmutov/next-ver 133 | [semantic-image]: https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg 134 | [semantic-url]: https://github.com/semantic-release/semantic-release 135 | [standard-image]: https://img.shields.io/badge/code%20style-standard-brightgreen.svg 136 | [standard-url]: http://standardjs.com/ 137 | -------------------------------------------------------------------------------- /__snapshots__/next-ver-spec.js: -------------------------------------------------------------------------------- 1 | exports['next-ver shows version on --version 1'] = ` 2 | 3 | command: node bin-path --version 4 | code: 0 5 | failed: false 6 | killed: false 7 | signal: null 8 | timedOut: false 9 | 10 | stdout: 11 | ------- 12 | next-ver 0.0.0-development 13 | Tells you the next semantic version for your local package 14 | ------- 15 | stderr: 16 | ------- 17 | 18 | ------- 19 | 20 | ` 21 | 22 | exports['next-ver shows version on -v 1'] = ` 23 | 24 | command: node bin-path -v 25 | code: 0 26 | failed: false 27 | killed: false 28 | signal: null 29 | timedOut: false 30 | 31 | stdout: 32 | ------- 33 | next-ver 0.0.0-development 34 | Tells you the next semantic version for your local package 35 | ------- 36 | stderr: 37 | ------- 38 | 39 | ------- 40 | 41 | ` 42 | 43 | exports['next-ver shows only version number on --silent 1'] = ` 44 | 45 | command: node bin-path --silent 46 | code: 0 47 | failed: false 48 | killed: false 49 | signal: null 50 | timedOut: false 51 | 52 | stdout: 53 | ------- 54 | 1.8.0 55 | ------- 56 | stderr: 57 | ------- 58 | 59 | ------- 60 | 61 | ` 62 | -------------------------------------------------------------------------------- /bin/next-ver.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const decideStartVersion = require('latest-version-or-tag') 4 | const {computeNextVersion} = require('..') 5 | const la = require('lazy-ass') 6 | const is = require('check-more-types') 7 | const debug = require('debug')('next-ver') 8 | const npmUtils = require('npm-utils') 9 | const R = require('ramda') 10 | 11 | const options = { 12 | alias: { 13 | version: 'v' 14 | } 15 | } 16 | 17 | const args = require('minimist')(process.argv.slice(2), options) 18 | 19 | if (args.version) { 20 | const pkg = require('../package.json') 21 | console.log('%s %s', pkg.name, pkg.version) 22 | console.log(pkg.description) 23 | process.exit(0) 24 | } 25 | 26 | function setVersion (newVersion) { 27 | if (!args.go) { 28 | debug('skipping "npm version" command, because no --go option set') 29 | return 30 | } 31 | if (!newVersion) { 32 | debug('skipping "npm version" command, no new version') 33 | return 34 | } 35 | debug(`running "npm version %s" command`, newVersion) 36 | la(is.semver(newVersion), 'invalid new version', newVersion) 37 | 38 | return npmUtils.incrementVersion({ 39 | increment: newVersion 40 | }) 41 | } 42 | 43 | function printVersion (nextVersion) { 44 | if (!nextVersion) { 45 | console.log('no new version judging by commits') 46 | return 47 | } 48 | if (args.silent) { 49 | console.log(nextVersion) 50 | } else { 51 | console.log('next version should be', nextVersion) 52 | } 53 | } 54 | 55 | decideStartVersion() 56 | .then(computeNextVersion) 57 | .then(R.tap(printVersion)) 58 | .then(setVersion) 59 | .done() 60 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "next-ver", 3 | "description": "Tells you the next semantic version for your local package", 4 | "version": "0.0.0-development", 5 | "author": "Gleb Bahmutov ", 6 | "bugs": "https://github.com/bahmutov/next-ver/issues", 7 | "bin": { 8 | "next-ver": "bin/next-ver.js" 9 | }, 10 | "preferGlobal": true, 11 | "config": { 12 | "pre-git": { 13 | "commit-msg": "simple", 14 | "pre-commit": [ 15 | "npm prune", 16 | "npm run deps", 17 | "npm test", 18 | "npm run ban" 19 | ], 20 | "pre-push": [ 21 | "npm run license", 22 | "npm run ban -- --all", 23 | "npm run size" 24 | ], 25 | "post-commit": [], 26 | "post-merge": [] 27 | } 28 | }, 29 | "engines": { 30 | "node": ">=6" 31 | }, 32 | "files": [ 33 | "bin", 34 | "src/*.js", 35 | "!src/*-spec.js" 36 | ], 37 | "homepage": "https://github.com/bahmutov/next-ver#readme", 38 | "keywords": [ 39 | "next", 40 | "semantic", 41 | "semver", 42 | "version" 43 | ], 44 | "license": "MIT", 45 | "main": "src/", 46 | "noScopeName": "next-ver", 47 | "publishConfig": { 48 | "registry": "http://registry.npmjs.org/" 49 | }, 50 | "repository": { 51 | "type": "git", 52 | "url": "https://github.com/bahmutov/next-ver.git" 53 | }, 54 | "scripts": { 55 | "ban": "ban", 56 | "deps": "deps-ok && dependency-check .", 57 | "issues": "git-issues", 58 | "license": "license-checker --production --onlyunknown --csv", 59 | "lint": "standard --verbose --fix bin/*.js src/*.js", 60 | "pretest": "npm run lint", 61 | "secure": "nsp check", 62 | "size": "t=\"$(npm pack .)\"; wc -c \"${t}\"; tar tvf \"${t}\"; rm \"${t}\";", 63 | "test": "npm run unit", 64 | "unit": "mocha src/*-spec.js", 65 | "dont-break": "dont-break --timeout 180", 66 | "semantic-release": "semantic-release pre && npm publish && semantic-release post" 67 | }, 68 | "devDependencies": { 69 | "ban-sensitive-files": "1.9.0", 70 | "dependency-check": "2.9.1", 71 | "deps-ok": "1.2.1", 72 | "dont-break": "1.8.0", 73 | "execa-wrap": "1.1.0", 74 | "git-issues": "1.3.1", 75 | "license-checker": "14.0.0", 76 | "mocha": "3.5.3", 77 | "nsp": "2.8.1", 78 | "pre-git": "3.15.3", 79 | "semantic-release": "^8.0.3", 80 | "snap-shot-it": "4.0.1", 81 | "standard": "10.0.3" 82 | }, 83 | "dependencies": { 84 | "check-more-types": "2.24.0", 85 | "debug": "3.1.0", 86 | "ggit": "2.3.0", 87 | "largest-semantic-change": "1.0.0", 88 | "latest-version-or-tag": "1.2.0", 89 | "lazy-ass": "1.6.0", 90 | "minimist": "1.2.0", 91 | "npm-utils": "2.0.0", 92 | "ramda": "0.24.1", 93 | "semver": "5.4.1", 94 | "simple-commit-message": "3.3.1" 95 | }, 96 | "release": { 97 | "analyzeCommits": "simple-commit-message" 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/compute-next-version.js: -------------------------------------------------------------------------------- 1 | const R = require('ramda') 2 | const la = require('lazy-ass') 3 | const is = require('check-more-types') 4 | const increment = require('./increment') 5 | const debug = require('debug')('next-ver') 6 | const ggit = require('ggit') 7 | const computeTopChange = require('largest-semantic-change').topChange 8 | const parseCommit = require('./parse-commit') 9 | 10 | function addSemverInformation (commits) { 11 | return commits.map(parseCommit) 12 | } 13 | 14 | function onlySemanticCommits (commits) { 15 | return commits.filter(R.prop('semver')) 16 | } 17 | 18 | function printFoundSemanticCommits (commits) { 19 | debug('semantic commits') 20 | debug(commits) 21 | la(is.array(commits), 'expected list of commits', commits) 22 | } 23 | 24 | function printChange (feat) { 25 | debug('semantic change "%s"', feat) 26 | la(is.maybe.string(feat), 'expected change to be a string', feat) 27 | } 28 | 29 | function printCommitsAfterTag (list) { 30 | debug('commits after last tag') 31 | debug(JSON.stringify(list, null, 2)) 32 | } 33 | 34 | function computeNextVersion (currentVersionTag) { 35 | la(is.unemptyString(currentVersionTag), 36 | 'missing current version', currentVersionTag) 37 | 38 | const incrementVersion = increment.bind(null, currentVersionTag) 39 | 40 | return ggit.commits.afterLastTag() 41 | .then(R.tap(printCommitsAfterTag)) 42 | .then(addSemverInformation) 43 | .then(onlySemanticCommits) 44 | .then(R.tap(printFoundSemanticCommits)) 45 | .then(R.map(R.prop('semver'))) 46 | .then(computeTopChange) 47 | .then(R.tap(printChange)) 48 | .then(incrementVersion) 49 | } 50 | 51 | module.exports = computeNextVersion 52 | -------------------------------------------------------------------------------- /src/increment.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const la = require('lazy-ass') 4 | const is = require('check-more-types') 5 | const debug = require('debug')('next-ver') 6 | const semver = require('semver') 7 | 8 | /* 9 | given current version, like "2.3.1" and a change type 10 | "break", "feat", "fix" or undefined 11 | returns the next version. 12 | 13 | 2.3.1 + "break" -> 3.0.0 14 | 2.3.1 + "feat" -> 2.4.0 15 | 2.3.1 + "fix" -> 2.3.2 16 | 17 | everything else should not change the version 18 | */ 19 | function increment (version, type) { 20 | debug('incrementing %s by %s', version, type) 21 | if (!type) { 22 | return 23 | } 24 | la(isSemanticChange(type), 'invalid change', type) 25 | 26 | if (!is.semver(version)) { 27 | debug('cleaning starting version %s', version) 28 | version = semver.clean(version) 29 | debug('got version %s', version) 30 | } 31 | 32 | // cannot use check-more-types.semver because it is too 33 | // strict and does not allow things like "0.0.0-alpha" 34 | // la(is.semver(version), 'invalid starting version', version) 35 | la(is.unemptyString(version), 'missing starting version', version) 36 | 37 | const semverIncrement = semverType(type) 38 | if (!semverIncrement) { 39 | return 40 | } 41 | return semver.inc(version, semverIncrement) 42 | } 43 | 44 | function isSemanticChange (type) { 45 | return is.oneOf(['major', 'feat', 'fix', 'chore'], type) 46 | } 47 | 48 | function semverType (type) { 49 | const types = { 50 | major: 'major', 51 | feat: 'minor', 52 | fix: 'patch' 53 | } 54 | return types[type] 55 | } 56 | 57 | module.exports = increment 58 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = { 4 | increment: require('./increment'), 5 | computeNextVersion: require('./compute-next-version') 6 | } 7 | -------------------------------------------------------------------------------- /src/next-ver-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const execaWrap = require('execa-wrap') 4 | const snapshot = require('snap-shot-it') 5 | const path = require('path') 6 | 7 | /* global describe, it */ 8 | describe('next-ver', () => { 9 | const bin = path.join(__dirname, '..', 'bin', 'next-ver.js') 10 | 11 | const normalize = (s) => 12 | s.replace(bin, 'bin-path') 13 | 14 | it('shows version on --version', function () { 15 | this.timeout(10000) 16 | return execaWrap('node', [bin, '--version']) 17 | .then(normalize) 18 | .then(snapshot) 19 | }) 20 | 21 | it('shows version on -v', function () { 22 | this.timeout(10000) 23 | return execaWrap('node', [bin, '-v']) 24 | .then(normalize) 25 | .then(snapshot) 26 | }) 27 | 28 | it('shows only version number on --silent', function () { 29 | this.timeout(10000) 30 | return execaWrap('node', [bin, '--silent']) 31 | .then(normalize) 32 | .then(snapshot) 33 | }) 34 | }) 35 | -------------------------------------------------------------------------------- /src/parse-commit.js: -------------------------------------------------------------------------------- 1 | const simpleParser = require('simple-commit-message') 2 | const R = require('ramda') 3 | 4 | function parseCommit (commit) { 5 | const semantic = simpleParser.parse(commit.message) 6 | const result = R.clone(commit) 7 | result.semver = semantic 8 | return result 9 | } 10 | 11 | module.exports = parseCommit 12 | --------------------------------------------------------------------------------