Skip to content

Commit

Permalink
feat: separate cli and defaults from base functionality
Browse files Browse the repository at this point in the history
The cli code now lives in cli.js, defaults in defaults.json and base
functionality in index.js. As a result, standard-version can now be
used as a library. See cli.js for usage. Calls to exec and
handleExecError have been refactored into a single handledExec
function that takes an error and success callback separately.
  • Loading branch information
Tapppi committed Sep 27, 2016
1 parent 28ff65a commit 34a6a4e
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 128 deletions.
19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ 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.

## Usage
## CLI Usage

### First Release

Expand Down Expand Up @@ -120,6 +120,23 @@ npm run release -- --help
standard-version --help
```

## Code usage

```js
var standardVersion = require('standard-version')

// Options are the same as command line, except camelCase
standardVersion({
noVerify: true,
infile: 'docs/CHANGELOG.md'
}, function (err) {
if (err) {
console.error(`standard-version failed with message: ${err.message}`)
}
// standard-version is done
})
```

## Commit Message Convention, at a Glance

_patches:_
Expand Down
52 changes: 52 additions & 0 deletions cli.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#!/usr/bin/env node
var standardVersion = require('./index')
var defaults = require('./defaults')

var argv = require('yargs')
.usage('Usage: $0 [options]')
.option('infile', {
alias: 'i',
describe: 'Read the CHANGELOG from this file',
default: defaults.infile,
global: true
})
.option('message', {
alias: 'm',
describe: 'Commit message, replaces %s with new version',
type: 'string',
default: defaults.message,
global: true
})
.option('first-release', {
alias: 'f',
describe: 'Is this the first release?',
type: 'boolean',
default: defaults.firstRelease,
global: true
})
.option('sign', {
alias: 's',
describe: 'Should the git commit and tag be signed?',
type: 'boolean',
default: defaults.sign,
global: true
})
.option('no-verify', {
alias: 'n',
describe: 'Bypass pre-commit or commit-msg git hooks during the commit phase',
type: 'boolean',
default: defaults.noVerify,
global: true
})
.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')
.wrap(97)
.argv

standardVersion(argv, function (err) {
if (err) {
process.exit(1)
}
})
7 changes: 7 additions & 0 deletions defaults.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"infile": "CHANGELOG.md",
"message": "chore(release): %s",
"firstRelease": false,
"sign": false,
"noVerify": false,
}
168 changes: 74 additions & 94 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,84 +1,54 @@
#!/usr/bin/env node
var conventionalRecommendedBump = require('conventional-recommended-bump')
var conventionalChangelog = require('conventional-changelog')
var path = require('path')
var argv = require('yargs')
.usage('Usage: $0 [options]')
.option('infile', {
alias: 'i',
describe: 'Read the CHANGELOG from this file',
default: 'CHANGELOG.md',
global: true
})
.option('message', {
alias: 'm',
describe: 'Commit message, replaces %s with new version',
type: 'string',
default: 'chore(release): %s',
global: true
})
.option('first-release', {
alias: 'f',
describe: 'Is this the first release?',
type: 'boolean',
default: false,
global: true
})
.option('sign', {
alias: 's',
describe: 'Should the git commit and tag be signed?',
type: 'boolean',
default: false,
global: true
})
.option('no-verify', {
alias: 'n',
describe: 'Bypass pre-commit or commit-msg git hooks during the commit phase',
type: 'boolean',
default: false,
global: true
})
.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')
.wrap(97)
.argv

var chalk = require('chalk')
var figures = require('figures')
var exec = require('child_process').exec
var fs = require('fs')
var accessSync = require('fs-access').sync
var pkgPath = path.resolve(process.cwd(), './package.json')
var pkg = require(pkgPath)
var semver = require('semver')
var util = require('util')
var objectAssign = require('object-assign')

conventionalRecommendedBump({
preset: 'angular'
}, function (err, release) {
if (err) {
console.error(chalk.red(err.message))
return
}
module.exports = function standardVersion (argv, done) {
var pkgPath = path.resolve(process.cwd(), './package.json')
var pkg = require(pkgPath)
var defaults = require('./defaults')

var newVersion = pkg.version
if (!argv.firstRelease) {
newVersion = semver.inc(pkg.version, release.releaseType)
checkpoint('bumping version in package.json from %s to %s', [pkg.version, newVersion])
pkg.version = newVersion
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf-8')
} else {
checkpoint('skip version bump on first release', [], chalk.red(figures.cross))
}
argv = objectAssign(defaults, argv)

conventionalRecommendedBump({
preset: 'angular'
}, function (err, release) {
if (err) {
printError(argv, err.message)
return done(err)
}

outputChangelog(argv, function () {
commit(argv, newVersion, function () {
return tag(newVersion, argv)
var newVersion = pkg.version
if (!argv.firstRelease) {
newVersion = semver.inc(pkg.version, release.releaseType)
checkpoint(argv, 'bumping version in package.json from %s to %s', [pkg.version, newVersion])
pkg.version = newVersion
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf-8')
} else {
checkpoint(argv, 'skip version bump on first release', [], chalk.red(figures.cross))
}

outputChangelog(argv, function (err) {
if (err) {
return done(err)
}
commit(argv, newVersion, function (err) {
if (err) {
return done(err)
}
return tag(newVersion, pkg.private, argv, done)
})
})
})
})
}

function outputChangelog (argv, cb) {
createIfMissing(argv)
Expand All @@ -92,31 +62,34 @@ function outputChangelog (argv, cb) {
var changelogStream = conventionalChangelog({
preset: 'angular'
})
.on('error', function (err) {
console.error(chalk.red(err.message))
process.exit(1)
})
.on('error', function (err) {
return cb(err)
})

changelogStream.on('data', function (buffer) {
content += buffer.toString()
})

changelogStream.on('end', function () {
checkpoint('outputting changes to %s', [argv.infile])
checkpoint(argv, 'outputting changes to %s', [argv.infile])
fs.writeFileSync(argv.infile, header + '\n' + (content + oldContent).replace(/\n+$/, '\n'), 'utf-8')
return cb()
})
}

function handleExecError (err, stderr) {
// If exec returns content in stderr, but no error, print it as a warning
// If exec returns an error, print it and exit with return code 1
if (err) {
console.error(chalk.red(stderr || err.message))
process.exit(1)
} else if (stderr) {
console.warn(chalk.yellow(stderr))
}
function handledExec (argv, cmd, errorCb, successCb) {
// Exec given cmd and handle possible errors
exec(cmd, function (err, stdout, stderr) {
// If exec returns content in stderr, but no error, print it as a warning
// If exec returns an error, print it and exit with return code 1
if (err) {
printError(argv, stderr || err.message)
return errorCb(err)
} else if (stderr) {
printError(argv, stderr, {level: 'warn', color: 'yellow'})
}
successCb()
})
}

function commit (argv, newVersion, cb) {
Expand All @@ -127,13 +100,11 @@ function commit (argv, newVersion, cb) {
msg += ' and %s'
args.unshift('package.json')
}
checkpoint(msg, args)
checkpoint(argv, msg, args)

exec('git add package.json ' + argv.infile, function (err, stdout, stderr) {
handleExecError(err, stderr)
exec('git commit ' + verify + (argv.sign ? '-S ' : '') + 'package.json ' + argv.infile + ' -m "' + formatCommitMessage(argv.message, newVersion) + '"', function (err, stdout, stderr) {
handleExecError(err, stderr)
return cb()
handledExec(argv, 'git add package.json ' + argv.infile, cb, function () {
handledExec(argv, 'git commit ' + verify + (argv.sign ? '-S ' : '') + 'package.json ' + argv.infile + ' -m "' + formatCommitMessage(argv.message, newVersion) + '"', cb, function () {
cb()
})
})
}
Expand All @@ -142,20 +113,20 @@ function formatCommitMessage (msg, newVersion) {
return String(msg).indexOf('%s') !== -1 ? util.format(msg, newVersion) : msg
}

function tag (newVersion, argv) {
function tag (newVersion, pkgPrivate, argv, cb) {
var tagOption
if (argv.sign) {
tagOption = '-s '
} else {
tagOption = '-a '
}
checkpoint('tagging release %s', [newVersion])
exec('git tag ' + tagOption + 'v' + newVersion + ' -m "' + formatCommitMessage(argv.message, newVersion) + '"', function (err, stdout, stderr) {
handleExecError(err, stderr)
checkpoint(argv, 'tagging release %s', [newVersion])
handledExec(argv, 'git tag ' + tagOption + 'v' + newVersion + ' -m "' + formatCommitMessage(argv.message, newVersion) + '"', cb, function () {
var message = 'git push --follow-tags origin master'
if (pkg.private !== true) message += '; npm publish'
if (pkgPrivate !== true) message += '; npm publish'

checkpoint('Run `%s` to publish', [message], chalk.blue(figures.info))
checkpoint(argv, 'Run `%s` to publish', [message], chalk.blue(figures.info))
cb()
})
}

Expand All @@ -164,15 +135,24 @@ function createIfMissing (argv) {
accessSync(argv.infile, fs.F_OK)
} catch (err) {
if (err.code === 'ENOENT') {
checkpoint('created %s', [argv.infile])
checkpoint(argv, 'created %s', [argv.infile])
argv.outputUnreleased = true
fs.writeFileSync(argv.infile, '\n', 'utf-8')
}
}
}

function checkpoint (msg, args, figure) {
function checkpoint (argv, msg, args, figure) {
console.info((figure || chalk.green(figures.tick)) + ' ' + util.format.apply(util, [msg].concat(args.map(function (arg) {
return chalk.bold(arg)
}))))
};
}

function printError (argv, msg, opts) {
opts = objectAssign({
level: 'error',
color: 'red'
}, opts)

console[opts.level](chalk[opts.color](msg))
}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "standard-version",
"version": "2.4.0",
"description": "replacement for `npm version` with automatic CHANGELOG generation",
"bin": "index.js",
"bin": "cli.js",
"scripts": {
"pretest": "standard",
"coverage": "nyc report --reporter=text-lcov | coveralls",
Expand Down Expand Up @@ -37,6 +37,7 @@
"conventional-recommended-bump": "^0.3.0",
"figures": "^1.5.0",
"fs-access": "^1.0.0",
"object-assign": "^4.1.0",
"semver": "^5.1.0",
"yargs": "^5.0.0"
},
Expand Down
Loading

0 comments on commit 34a6a4e

Please sign in to comment.