diff --git a/README.md b/README.md index f50c798d5..16c4ded5c 100644 --- a/README.md +++ b/README.md @@ -8,23 +8,23 @@ _Having problems? want to contribute? join our [community slack](http://devtoolscommunity.herokuapp.com)_. -> stop using `npm version`, use `standard-version` it rocks! -Automatic versioning and CHANGELOG generation, using GitHub's squash button and +Automate versioning and CHANGELOG generation, with [semver](https://semver.org/) and [conventional commit messages](https://conventionalcommits.org). _how it works:_ 1. when you land commits on your `master` branch, select the _Squash and Merge_ option. 2. add a title and body that follows the [Conventional Commits Specification](https://conventionalcommits.org). -3. when you're ready to release to npm: +3. when you're ready to release: 1. `git checkout master; git pull origin master` 2. run `standard-version` - 3. `git push --follow-tags origin master && npm publish` + 3. `git push --follow-tags origin master && npm publish + _(or, `docker push`, `gem push`, etc.)_ `standard-version` does the following: -1. bumps the version in _package.json/bower.json_ (based on your commit history) +1. bumps the version in metadata files (package.json, composer.json, etc). 2. uses [conventional-changelog](https://github.com/conventional-changelog/conventional-changelog) to update _CHANGELOG.md_ 3. commits _package.json (et al.)_ and _CHANGELOG.md_ 4. tags a new release @@ -181,7 +181,7 @@ Simply add the following to your package.json to configure lifecycle scripts: } ``` -As an example to change from using GitHub to track your items to using your projects Jira use a +As an example to change from using GitHub to track your items to using your projects Jira use a `postchangelog` script to replace the url fragment containing 'https://github.com/`myproject`/issues/' with a link to your Jira - assuming you have already installed [replace](https://www.npmjs.com/package/replace) ```json diff --git a/command.js b/command.js index c571494fc..095f9650c 100755 --- a/command.js +++ b/command.js @@ -72,6 +72,11 @@ module.exports = require('yargs') default: defaults.dryRun, describe: 'See the commands that running standard-version would run' }) + .option('git-tag-fallback', { + type: 'boolean', + default: defaults.gitTagFallback, + describe: `fallback to git tags for version, if no meta-information file is found (e.g., package.json)` + }) .check((argv) => { if (typeof argv.scripts !== 'object' || Array.isArray(argv.scripts)) { throw Error('scripts must be an object') diff --git a/defaults.json b/defaults.json index 2834fb345..b70e54697 100644 --- a/defaults.json +++ b/defaults.json @@ -9,5 +9,6 @@ "tagPrefix": "v", "scripts": {}, "skip": {}, - "dryRun": false + "dryRun": false, + "gitTagFallback": true } diff --git a/index.js b/index.js index 0c685e054..d8432b03c 100755 --- a/index.js +++ b/index.js @@ -1,3 +1,4 @@ +const latestSemverTag = require('./lib/latest-semver-tag') const path = require('path') const printError = require('./lib/print-error') @@ -7,7 +8,7 @@ const commit = require('./lib/lifecycles/commit') const tag = require('./lib/lifecycles/tag') module.exports = function standardVersion (argv) { - var pkg + let pkg bump.pkgFiles.forEach((filename) => { if (pkg) return var pkgPath = path.resolve(process.cwd(), filename) @@ -15,16 +16,25 @@ module.exports = function standardVersion (argv) { pkg = require(pkgPath) } catch (err) {} }) - if (!pkg) { - return Promise.reject(new Error('no package file found')) - } - var newVersion = pkg.version - var defaults = require('./defaults') - var args = Object.assign({}, defaults, argv) + let newVersion + let defaults = require('./defaults') + let args = Object.assign({}, defaults, argv) return Promise.resolve() .then(() => { - return bump(args, pkg) + if (!pkg && args.gitTagFallback) { + return latestSemverTag() + } else if (!pkg) { + throw new Error('no package file found') + } else { + return pkg.version + } + }) + .then(version => { + newVersion = version + }) + .then(() => { + return bump(args, newVersion) }) .then((_newVersion) => { // if bump runs, it calculaes the new version that we @@ -36,7 +46,7 @@ module.exports = function standardVersion (argv) { return commit(args, newVersion) }) .then(() => { - return tag(newVersion, pkg.private, args) + return tag(newVersion, pkg ? pkg.private : false, args) }) .catch((err) => { printError(args, err.message) diff --git a/lib/latest-semver-tag.js b/lib/latest-semver-tag.js new file mode 100644 index 000000000..7c63ec42a --- /dev/null +++ b/lib/latest-semver-tag.js @@ -0,0 +1,15 @@ +const gitSemverTags = require('git-semver-tags') +const semver = require('semver') + +module.exports = function () { + return new Promise((resolve, reject) => { + gitSemverTags(function (err, tags) { + if (err) return reject(err) + else if (!tags.length) return resolve('1.0.0') + // ensure that the largest semver tag is at the head. + tags = tags.map(tag => { return semver.clean(tag) }) + tags.sort(semver.rcompare) + return resolve(tags[0]) + }) + }) +} diff --git a/lib/lifecycles/bump.js b/lib/lifecycles/bump.js index b5fe61219..7fa0857b8 100644 --- a/lib/lifecycles/bump.js +++ b/lib/lifecycles/bump.js @@ -13,13 +13,13 @@ const writeFile = require('../write-file') var configsToUpdate = {} -function Bump (args, pkg) { +function Bump (args, version) { // reset the cache of updated config files each // time we perform the version bump step. configsToUpdate = {} if (args.skip.bump) return Promise.resolve() - var newVersion = pkg.version + var newVersion = version return runLifecycleScript(args, 'prerelease') .then(runLifecycleScript.bind(this, args, 'prebump')) .then((stdout) => { @@ -28,8 +28,8 @@ function Bump (args, pkg) { }) .then((release) => { if (!args.firstRelease) { - var releaseType = getReleaseType(args.prerelease, release.releaseType, pkg.version) - newVersion = semver.valid(releaseType) || semver.inc(pkg.version, releaseType, args.prerelease) + var releaseType = getReleaseType(args.prerelease, release.releaseType, version) + newVersion = semver.valid(releaseType) || semver.inc(version, releaseType, args.prerelease) updateConfigs(args, newVersion) } else { checkpoint(args, 'skip version bump on first release', [], chalk.red(figures.cross)) diff --git a/test.js b/test.js index d579cd663..ec891f9a8 100644 --- a/test.js +++ b/test.js @@ -2,16 +2,16 @@ 'use strict' -var shell = require('shelljs') -var fs = require('fs') -var path = require('path') -var stream = require('stream') -var mockGit = require('mock-git') -var mockery = require('mockery') -var semver = require('semver') -var formatCommitMessage = require('./lib/format-commit-message') -var cli = require('./command') -var standardVersion = require('./index') +const shell = require('shelljs') +const fs = require('fs') +const path = require('path') +const stream = require('stream') +const mockGit = require('mock-git') +const mockery = require('mockery') +const semver = require('semver') +const formatCommitMessage = require('./lib/format-commit-message') +const cli = require('./command') +const standardVersion = require('./index') require('chai').should() @@ -45,7 +45,6 @@ function writePackageJson (version, option) { option = option || {} var pkg = Object.assign(option, { version: version }) fs.writeFileSync('package.json', JSON.stringify(pkg), 'utf-8') - delete require.cache[require.resolve(path.join(process.cwd(), 'package.json'))] } function writeBowerJson (version, option) { @@ -96,6 +95,16 @@ function initInTempFolder () { shell.cd('tmp') shell.exec('git init') commit('root-commit') + ;['package.json', + 'manifest.json', + 'bower.json' + ].forEach(metadata => { + try { + delete require.cache[require.resolve(path.join(process.cwd(), metadata))] + } catch (err) { + // we haven't loaded the metadata file yet. + } + }) writePackageJson('1.0.0') } @@ -746,7 +755,10 @@ describe('standard-version', function () { describe('without a package file to bump', function () { it('should exit with error', function () { shell.rm('package.json') - return require('./index')({ silent: true }) + return require('./index')({ + silent: true, + gitTagFallback: false + }) .catch((err) => { err.message.should.equal('no package file found') }) @@ -886,4 +898,47 @@ describe('standard-version', function () { }) }) }) + + describe('.gitignore', () => { + beforeEach(function () { + writeBowerJson('1.0.0') + }) + + it('does not update files present in .gitignore', () => { + fs.writeFileSync('.gitignore', 'bower.json', 'utf-8') + + commit('feat: first commit') + shell.exec('git tag -a v1.0.0 -m "my awesome first release"') + commit('feat: new feature!') + return require('./index')({ silent: true }) + .then(() => { + JSON.parse(fs.readFileSync('bower.json', 'utf-8')).version.should.equal('1.0.0') + getPackageVersion().should.equal('1.1.0') + }) + }) + }) + + describe('gitTagFallback', () => { + it('defaults to 1.0.0 if no tags in git history', () => { + shell.rm('package.json') + commit('feat: first commit') + return require('./index')({ silent: true }) + .then(() => { + const output = shell.exec('git tag') + output.stdout.should.include('v1.1.0') + }) + }) + + it('bases version on last tag, if tags are found', () => { + shell.rm('package.json') + shell.exec('git tag -a v5.0.0 -m "a release"') + shell.exec('git tag -a v3.0.0 -m "another release"') + commit('feat: another commit') + return require('./index')({ silent: true }) + .then(() => { + const output = shell.exec('git tag') + output.stdout.should.include('v5.1.0') + }) + }) + }) })