Skip to content

Commit

Permalink
Merge branch 'master' into fix-ci-eslint
Browse files Browse the repository at this point in the history
  • Loading branch information
bcoe committed May 5, 2019
2 parents 32855f4 + 8317c9e commit b120db2
Show file tree
Hide file tree
Showing 9 changed files with 205 additions and 42 deletions.
37 changes: 36 additions & 1 deletion README.md
Expand Up @@ -65,6 +65,39 @@ Now you can use `standard-version` in place of `npm version`.

This has the benefit of allowing you to use `standard-version` on any repo/package without adding a dev dependency to each one.

## Configuration

You can configure `standard-version` either by:

1. Placing a `standard-version` stanza in your `package.json` (assuming
your project is JavaScript).
1. Creating a `.versionrc` or `.versionrc.json`.

Any of the command line paramters accepted by `standard-version` can instead
be provided via configuration.

### Customizing CHANGELOG Generation

By default, `standard-version` uses the [conventionalcommits preset](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-conventionalcommits).

This preset:

* adheres closely to the [conventionalcommits.org](https://www.conventionalcommits.org)
specification.
* is highly configurable, following the configuration specification
[maintained here](https://github.com/conventional-changelog/conventional-changelog-config-spec).
* _we've documented these config settings as a recommendation to other tooling makers._

There are a variety of dials and knobs you can turn related to CHANGELOG generation.

As an example, suppose you're using GitLab, rather than GitHub, you might modify the following variables:

* `commitUrlFormat`: the URL format of commit SHAs detected in commit messages.
* `compareUrlFormat`: the URL format used to compare two tags.
* `issueUrlFormat`: the URL format used to link to issues.

Making these URLs match GitLab's format, rather than GitHub's.

## CLI Usage

### First Release
Expand Down Expand Up @@ -232,14 +265,16 @@ standard-version --dry-run

### Prefix Tags

If you would like to prefix your tags with something, you can do so with the `-t` flag.
Tags are prefixed with `v` by default. If you would like to prefix your tags with something else, you can do so with the `-t` flag.

```sh
standard-version -t @scope/package\@
```

This will prefix your tags to look something like `@scope/package@2.0.0`

If you do not want to have any tag prefix you can use the `-t` flag without value.

### CLI Help

```sh
Expand Down
40 changes: 36 additions & 4 deletions command.js
@@ -1,6 +1,13 @@
let defaults = require('./defaults')
const findUp = require('find-up')
const defaults = require('./defaults')
const { readFileSync } = require('fs')

module.exports = require('yargs')
const configPath = findUp.sync(['.versionrc', '.version.json'])
const config = configPath ? JSON.parse(readFileSync(configPath)) : {}
const spec = require('conventional-changelog-config-spec')
const { START_OF_LAST_RELEASE_PATTERN } = require('./lib/lifecycles/changelog')

const yargs = require('yargs')
.usage('Usage: $0 [options]')
.option('release-as', {
alias: 'r',
Expand Down Expand Up @@ -81,6 +88,10 @@ module.exports = require('yargs')
type: 'string',
describe: 'Only populate commits made under this path'
})
.option('changelogHeader', {
type: 'string',
describe: 'Use a custom header when generating and updating changelog.'
})
.option('preset', {
type: 'string',
default: defaults.preset,
Expand All @@ -95,11 +106,32 @@ module.exports = require('yargs')
return true
}
})
.version()
.alias('version', 'v')
.help()
.alias('help', 'h')
.example('$0', 'Update changelog and tag release')
.example('$0 -m "%s: see changelog for details"', 'Update changelog and tag release with custom commit message')
.pkgConf('standard-version')
.config(config)
.wrap(97)
.check((args) => {
if (args.changelogHeader && args.changelogHeader.search(START_OF_LAST_RELEASE_PATTERN) !== -1) {
throw Error(`custom changelog header must not match ${START_OF_LAST_RELEASE_PATTERN}`)
} else {
return true
}
})

Object.keys(spec.properties).forEach(propertyKey => {
const property = spec.properties[propertyKey]
yargs.option(propertyKey, {
type: property.type,
describe: property.description,
default: property.default,
group: 'Preset Configuration:'
})
})

module.exports = yargs

// TODO: yargs should be populated with keys/descriptions from
// https://github.com/conventional-changelog/conventional-changelog-config-spec
4 changes: 2 additions & 2 deletions defaults.json
Expand Up @@ -11,5 +11,5 @@
"skip": {},
"dryRun": false,
"gitTagFallback": true,
"preset": "angular"
}
"preset": "conventionalcommits"
}
3 changes: 2 additions & 1 deletion lib/lifecycles/bump.js
Expand Up @@ -9,6 +9,7 @@ const figures = require('figures')
const fs = require('fs')
const DotGitignore = require('dotgitignore')
const path = require('path')
const presetLoader = require('../preset-loader')
const runLifecycleScript = require('../run-lifecycle-script')
const semver = require('semver')
const stringifyPackage = require('stringify-package')
Expand Down Expand Up @@ -137,7 +138,7 @@ function bumpVersion (releaseAs, args) {
} else {
conventionalRecommendedBump({
debug: args.verbose && console.info.bind(console, 'conventional-recommended-bump'),
preset: args.preset || 'angular',
preset: presetLoader(args),
path: args.path
}, function (err, release) {
if (err) return reject(err)
Expand Down
19 changes: 13 additions & 6 deletions lib/lifecycles/changelog.js
Expand Up @@ -3,10 +3,12 @@ const chalk = require('chalk')
const checkpoint = require('../checkpoint')
const conventionalChangelog = require('conventional-changelog')
const fs = require('fs')
const presetLoader = require('../preset-loader')
const runLifecycleScript = require('../run-lifecycle-script')
const writeFile = require('../write-file')
const START_OF_LAST_RELEASE_PATTERN = /(^#+ \[?[0-9]+\.[0-9]+\.[0-9]+|<a name=)/m

module.exports = function (args, newVersion) {
function Changelog (args, newVersion) {
if (args.skip.changelog) return Promise.resolve()
return runLifecycleScript(args, 'prechangelog')
.then(() => {
Expand All @@ -17,22 +19,27 @@ module.exports = function (args, newVersion) {
})
}

Changelog.START_OF_LAST_RELEASE_PATTERN = START_OF_LAST_RELEASE_PATTERN

module.exports = Changelog

function outputChangelog (args, newVersion) {
return new Promise((resolve, reject) => {
createIfMissing(args)
let header = '# Change Log\n\nAll notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.\n'
const header = args.changelogHeader || '# Changelog\n\nAll notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.\n'

let oldContent = args.dryRun ? '' : fs.readFileSync(args.infile, 'utf-8')
let oldContentStart = oldContent.search(START_OF_LAST_RELEASE_PATTERN)
// find the position of the last release and remove header:
const changelogSectionRegExp = /<a name=|##? \[?[0-9]+\.[0-9]+\.[0-9]+\]?/
if (oldContent.search(changelogSectionRegExp) !== -1) {
oldContent = oldContent.substring(oldContent.search(changelogSectionRegExp))
if (oldContentStart !== -1) {
oldContent = oldContent.substring(oldContentStart)
}
let content = ''
let context
if (args.dryRun) context = { version: newVersion }
let changelogStream = conventionalChangelog({
debug: args.verbose && console.info.bind(console, 'conventional-changelog'),
preset: args.preset || 'angular',
preset: presetLoader(args),
tagPrefix: args.tagPrefix
}, context, { merges: null, path: args.path })
.on('error', function (err) {
Expand Down
16 changes: 16 additions & 0 deletions lib/preset-loader.js
@@ -0,0 +1,16 @@
// TODO: this should be replaced with an object we maintain and
// describe in: https://github.com/conventional-changelog/conventional-changelog-config-spec
const spec = require('conventional-changelog-config-spec')

module.exports = (args) => {
let preset = args.preset || 'conventionalcommits'
if (preset === 'conventionalcommits') {
preset = {
name: preset
}
Object.keys(spec.properties).forEach(key => {
if (args[key] !== undefined) preset[key] = args[key]
})
}
return preset
}
54 changes: 28 additions & 26 deletions package.json
Expand Up @@ -4,7 +4,7 @@
"description": "replacement for `npm version` with automatic CHANGELOG generation",
"bin": "bin/cli.js",
"scripts": {
"pretest": "eslint .",
"posttest": "eslint .",
"coverage": "nyc report --reporter=text-lcov | coveralls",
"test": "nyc mocha --timeout=20000 test.js",
"release": "bin/cli.js"
Expand Down Expand Up @@ -38,32 +38,34 @@
},
"homepage": "https://github.com/conventional-changelog/standard-version#readme",
"dependencies": {
"chalk": "^2.4.1",
"conventional-changelog": "^3.0.6",
"conventional-recommended-bump": "^4.0.4",
"detect-indent": "^5.0.0",
"detect-newline": "^2.1.0",
"dotgitignore": "^2.1.0",
"figures": "^2.0.0",
"fs-access": "^1.0.0",
"git-semver-tags": "^2.0.2",
"semver": "^5.2.0",
"stringify-package": "^1.0.0",
"yargs": "^12.0.2"
"chalk": "2.4.2",
"conventional-changelog": "3.1.8",
"conventional-changelog-config-spec": "1.0.0",
"conventional-recommended-bump": "5.0.0",
"detect-indent": "5.0.0",
"detect-newline": "3.0.0",
"dotgitignore": "2.1.0",
"figures": "3.0.0",
"find-up": "3.0.0",
"fs-access": "1.0.1",
"git-semver-tags": "2.0.2",
"semver": "6.0.0",
"stringify-package": "1.0.0",
"yargs": "13.2.2"
},
"devDependencies": {
"chai": "^3.5.0",
"coveralls": "^3.0.1",
"eslint": "^5.16.0",
"eslint-config-standard": "^12.0.0",
"eslint-plugin-import": "^2.16.0",
"eslint-plugin-node": "^8.0.1",
"eslint-plugin-promise": "^4.0.1",
"eslint-plugin-standard": "^4.0.0",
"mocha": "^5.2.0",
"mock-git": "^1.0.3",
"mockery": "^2.0.0",
"nyc": "^13.3.0",
"shelljs": "^0.7.8"
"chai": "3.5.0",
"coveralls": "3.0.3",
"eslint": "5.16.0",
"eslint-config-standard": "12.0.0",
"eslint-plugin-import": "2.17.2",
"eslint-plugin-node": "8.0.1",
"eslint-plugin-promise": "4.1.1",
"eslint-plugin-standard": "4.0.0",
"mocha": "5.2.0",
"mock-git": "1.0.3",
"mockery": "2.1.0",
"nyc": "14.1.0",
"shelljs": "0.7.8"
}
}
4 changes: 3 additions & 1 deletion renovate.json
@@ -1,3 +1,5 @@
{
"enabled": false
"extends": [
"config:base"
]
}
70 changes: 69 additions & 1 deletion test.js
Expand Up @@ -94,6 +94,7 @@ function initInTempFolder () {
shell.mkdir('tmp')
shell.cd('tmp')
shell.exec('git init')
shell.exec('git config commit.gpgSign false')
commit('root-commit')
writePackageJson('1.0.0')
}
Expand Down Expand Up @@ -151,7 +152,7 @@ describe('cli', function () {
})

describe('CHANGELOG.md exists', function () {
it('appends the new release above the last release, removing the old header', function () {
it('appends the new release above the last release, removing the old header (legacy format)', function () {
fs.writeFileSync('CHANGELOG.md', 'legacy header format<a name="1.0.0">\n', 'utf-8')

commit('feat: first commit')
Expand All @@ -164,6 +165,33 @@ describe('cli', function () {
content.should.not.match(/legacy header format/)
})

// TODO: we should use snapshots which are easier to update than large
// string assertions; we should also consider not using the CLI which
// is slower than calling standard-version directly.
it('appends the new release above the last release, removing the old header (new format)', function () {
// we don't create a package.json, so no {{host}} and {{repo}} tag
// will be populated, let's use a compareUrlFormat without these.
const cliArgs = '--compareUrlFormat=/compare/{{previousTag}}...{{currentTag}}'

commit('feat: first commit')
shell.exec('git tag -a v1.0.0 -m "my awesome first release"')
commit('fix: patch release')

execCli(cliArgs).code.should.equal(0)
let content = fs.readFileSync('CHANGELOG.md', 'utf-8')

// remove commit hashes and dates to make testing against a static string easier:
content = content.replace(/patch release [0-9a-f]{6,8}/g, 'patch release ABCDEFXY').replace(/\([0-9]{4}-[0-9]{2}-[0-9]{2}\)/g, '(YYYY-MM-DD)')
content.should.equal('# Changelog\n\nAll notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.\n\n### [1.0.1](/compare/v1.0.0...v1.0.1) (YYYY-MM-DD)\n\n\n### Bug Fixes\n\n* patch release ABCDEFXY\n')

commit('fix: another patch release')
// we've populated no package.json, so no {{host}} and
execCli(cliArgs).code.should.equal(0)
content = fs.readFileSync('CHANGELOG.md', 'utf-8')
content = content.replace(/patch release [0-9a-f]{6,8}/g, 'patch release ABCDEFXY').replace(/\([0-9]{4}-[0-9]{2}-[0-9]{2}\)/g, '(YYYY-MM-DD)')
content.should.equal('# Changelog\n\nAll notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.\n\n### [1.0.2](/compare/v1.0.1...v1.0.2) (YYYY-MM-DD)\n\n\n### Bug Fixes\n\n* another patch release ABCDEFXY\n\n\n\n### [1.0.1](/compare/v1.0.0...v1.0.1) (YYYY-MM-DD)\n\n\n### Bug Fixes\n\n* patch release ABCDEFXY\n')
})

it('commits all staged files', function () {
fs.writeFileSync('CHANGELOG.md', 'legacy header format<a name="1.0.0">\n', 'utf-8')

Expand All @@ -186,6 +214,20 @@ describe('cli', function () {
content.should.match(/1\.0\.1/)
content.should.not.match(/legacy header format/)
})

it('allows for a custom changelog header', function () {
fs.writeFileSync('CHANGELOG.md', '', 'utf-8')
commit('feat: first commit')
execCli('--changelogHeader="# Pork Chop Log"').code.should.equal(0)
let content = fs.readFileSync('CHANGELOG.md', 'utf-8')
content.should.match(/# Pork Chop Log/)
})

it('exits with error if changelog header matches last version search regex', function () {
fs.writeFileSync('CHANGELOG.md', '', 'utf-8')
commit('feat: first commit')
execCli('--changelogHeader="## 3.0.2"').code.should.equal(1)
})
})

describe('with mocked git', function () {
Expand Down Expand Up @@ -989,4 +1031,30 @@ describe('standard-version', function () {
})
})
})

describe('configuration', () => {
it('reads config from .versionrc', function () {
// we currently skip several replacments in CHANGELOG
// generation if repository URL isn't set.
//
// TODO: consider modifying this logic in conventional-commits
// perhaps we should only skip the replacement if we rely on
// the {{host}} field?
writePackageJson('1.0.0', {
repository: {
url: 'https://github.com/yargs/yargs.git'
}
})
// write configuration that overrides default issue
// URL format.
fs.writeFileSync('.versionrc', JSON.stringify({
issueUrlFormat: 'http://www.foo.com/{{id}}'
}), 'utf-8')
commit('feat: another commit addresses issue #1')
execCli()
// CHANGELOG should have the new issue URL format.
const content = fs.readFileSync('CHANGELOG.md', 'utf-8')
content.should.include('http://www.foo.com/1')
})
})
})

0 comments on commit b120db2

Please sign in to comment.