Skip to content

Commit 34a6a4e

Browse files
committed
feat: separate cli and defaults from base functionality
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.
1 parent 28ff65a commit 34a6a4e

File tree

6 files changed

+171
-128
lines changed

6 files changed

+171
-128
lines changed

README.md

+18-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ Now you can use `standard-version` in place of `npm version`.
6464

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

67-
## Usage
67+
## CLI Usage
6868

6969
### First Release
7070

@@ -120,6 +120,23 @@ npm run release -- --help
120120
standard-version --help
121121
```
122122

123+
## Code usage
124+
125+
```js
126+
var standardVersion = require('standard-version')
127+
128+
// Options are the same as command line, except camelCase
129+
standardVersion({
130+
noVerify: true,
131+
infile: 'docs/CHANGELOG.md'
132+
}, function (err) {
133+
if (err) {
134+
console.error(`standard-version failed with message: ${err.message}`)
135+
}
136+
// standard-version is done
137+
})
138+
```
139+
123140
## Commit Message Convention, at a Glance
124141

125142
_patches:_

cli.js

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#!/usr/bin/env node
2+
var standardVersion = require('./index')
3+
var defaults = require('./defaults')
4+
5+
var argv = require('yargs')
6+
.usage('Usage: $0 [options]')
7+
.option('infile', {
8+
alias: 'i',
9+
describe: 'Read the CHANGELOG from this file',
10+
default: defaults.infile,
11+
global: true
12+
})
13+
.option('message', {
14+
alias: 'm',
15+
describe: 'Commit message, replaces %s with new version',
16+
type: 'string',
17+
default: defaults.message,
18+
global: true
19+
})
20+
.option('first-release', {
21+
alias: 'f',
22+
describe: 'Is this the first release?',
23+
type: 'boolean',
24+
default: defaults.firstRelease,
25+
global: true
26+
})
27+
.option('sign', {
28+
alias: 's',
29+
describe: 'Should the git commit and tag be signed?',
30+
type: 'boolean',
31+
default: defaults.sign,
32+
global: true
33+
})
34+
.option('no-verify', {
35+
alias: 'n',
36+
describe: 'Bypass pre-commit or commit-msg git hooks during the commit phase',
37+
type: 'boolean',
38+
default: defaults.noVerify,
39+
global: true
40+
})
41+
.help()
42+
.alias('help', 'h')
43+
.example('$0', 'Update changelog and tag release')
44+
.example('$0 -m "%s: see changelog for details"', 'Update changelog and tag release with custom commit message')
45+
.wrap(97)
46+
.argv
47+
48+
standardVersion(argv, function (err) {
49+
if (err) {
50+
process.exit(1)
51+
}
52+
})

defaults.json

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"infile": "CHANGELOG.md",
3+
"message": "chore(release): %s",
4+
"firstRelease": false,
5+
"sign": false,
6+
"noVerify": false,
7+
}

index.js

+74-94
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,54 @@
1-
#!/usr/bin/env node
21
var conventionalRecommendedBump = require('conventional-recommended-bump')
32
var conventionalChangelog = require('conventional-changelog')
43
var path = require('path')
5-
var argv = require('yargs')
6-
.usage('Usage: $0 [options]')
7-
.option('infile', {
8-
alias: 'i',
9-
describe: 'Read the CHANGELOG from this file',
10-
default: 'CHANGELOG.md',
11-
global: true
12-
})
13-
.option('message', {
14-
alias: 'm',
15-
describe: 'Commit message, replaces %s with new version',
16-
type: 'string',
17-
default: 'chore(release): %s',
18-
global: true
19-
})
20-
.option('first-release', {
21-
alias: 'f',
22-
describe: 'Is this the first release?',
23-
type: 'boolean',
24-
default: false,
25-
global: true
26-
})
27-
.option('sign', {
28-
alias: 's',
29-
describe: 'Should the git commit and tag be signed?',
30-
type: 'boolean',
31-
default: false,
32-
global: true
33-
})
34-
.option('no-verify', {
35-
alias: 'n',
36-
describe: 'Bypass pre-commit or commit-msg git hooks during the commit phase',
37-
type: 'boolean',
38-
default: false,
39-
global: true
40-
})
41-
.help()
42-
.alias('help', 'h')
43-
.example('$0', 'Update changelog and tag release')
44-
.example('$0 -m "%s: see changelog for details"', 'Update changelog and tag release with custom commit message')
45-
.wrap(97)
46-
.argv
474

485
var chalk = require('chalk')
496
var figures = require('figures')
507
var exec = require('child_process').exec
518
var fs = require('fs')
529
var accessSync = require('fs-access').sync
53-
var pkgPath = path.resolve(process.cwd(), './package.json')
54-
var pkg = require(pkgPath)
5510
var semver = require('semver')
5611
var util = require('util')
12+
var objectAssign = require('object-assign')
5713

58-
conventionalRecommendedBump({
59-
preset: 'angular'
60-
}, function (err, release) {
61-
if (err) {
62-
console.error(chalk.red(err.message))
63-
return
64-
}
14+
module.exports = function standardVersion (argv, done) {
15+
var pkgPath = path.resolve(process.cwd(), './package.json')
16+
var pkg = require(pkgPath)
17+
var defaults = require('./defaults')
6518

66-
var newVersion = pkg.version
67-
if (!argv.firstRelease) {
68-
newVersion = semver.inc(pkg.version, release.releaseType)
69-
checkpoint('bumping version in package.json from %s to %s', [pkg.version, newVersion])
70-
pkg.version = newVersion
71-
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf-8')
72-
} else {
73-
checkpoint('skip version bump on first release', [], chalk.red(figures.cross))
74-
}
19+
argv = objectAssign(defaults, argv)
20+
21+
conventionalRecommendedBump({
22+
preset: 'angular'
23+
}, function (err, release) {
24+
if (err) {
25+
printError(argv, err.message)
26+
return done(err)
27+
}
7528

76-
outputChangelog(argv, function () {
77-
commit(argv, newVersion, function () {
78-
return tag(newVersion, argv)
29+
var newVersion = pkg.version
30+
if (!argv.firstRelease) {
31+
newVersion = semver.inc(pkg.version, release.releaseType)
32+
checkpoint(argv, 'bumping version in package.json from %s to %s', [pkg.version, newVersion])
33+
pkg.version = newVersion
34+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf-8')
35+
} else {
36+
checkpoint(argv, 'skip version bump on first release', [], chalk.red(figures.cross))
37+
}
38+
39+
outputChangelog(argv, function (err) {
40+
if (err) {
41+
return done(err)
42+
}
43+
commit(argv, newVersion, function (err) {
44+
if (err) {
45+
return done(err)
46+
}
47+
return tag(newVersion, pkg.private, argv, done)
48+
})
7949
})
8050
})
81-
})
51+
}
8252

8353
function outputChangelog (argv, cb) {
8454
createIfMissing(argv)
@@ -92,31 +62,34 @@ function outputChangelog (argv, cb) {
9262
var changelogStream = conventionalChangelog({
9363
preset: 'angular'
9464
})
95-
.on('error', function (err) {
96-
console.error(chalk.red(err.message))
97-
process.exit(1)
98-
})
65+
.on('error', function (err) {
66+
return cb(err)
67+
})
9968

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

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

111-
function handleExecError (err, stderr) {
112-
// If exec returns content in stderr, but no error, print it as a warning
113-
// If exec returns an error, print it and exit with return code 1
114-
if (err) {
115-
console.error(chalk.red(stderr || err.message))
116-
process.exit(1)
117-
} else if (stderr) {
118-
console.warn(chalk.yellow(stderr))
119-
}
80+
function handledExec (argv, cmd, errorCb, successCb) {
81+
// Exec given cmd and handle possible errors
82+
exec(cmd, function (err, stdout, stderr) {
83+
// If exec returns content in stderr, but no error, print it as a warning
84+
// If exec returns an error, print it and exit with return code 1
85+
if (err) {
86+
printError(argv, stderr || err.message)
87+
return errorCb(err)
88+
} else if (stderr) {
89+
printError(argv, stderr, {level: 'warn', color: 'yellow'})
90+
}
91+
successCb()
92+
})
12093
}
12194

12295
function commit (argv, newVersion, cb) {
@@ -127,13 +100,11 @@ function commit (argv, newVersion, cb) {
127100
msg += ' and %s'
128101
args.unshift('package.json')
129102
}
130-
checkpoint(msg, args)
103+
checkpoint(argv, msg, args)
131104

132-
exec('git add package.json ' + argv.infile, function (err, stdout, stderr) {
133-
handleExecError(err, stderr)
134-
exec('git commit ' + verify + (argv.sign ? '-S ' : '') + 'package.json ' + argv.infile + ' -m "' + formatCommitMessage(argv.message, newVersion) + '"', function (err, stdout, stderr) {
135-
handleExecError(err, stderr)
136-
return cb()
105+
handledExec(argv, 'git add package.json ' + argv.infile, cb, function () {
106+
handledExec(argv, 'git commit ' + verify + (argv.sign ? '-S ' : '') + 'package.json ' + argv.infile + ' -m "' + formatCommitMessage(argv.message, newVersion) + '"', cb, function () {
107+
cb()
137108
})
138109
})
139110
}
@@ -142,20 +113,20 @@ function formatCommitMessage (msg, newVersion) {
142113
return String(msg).indexOf('%s') !== -1 ? util.format(msg, newVersion) : msg
143114
}
144115

145-
function tag (newVersion, argv) {
116+
function tag (newVersion, pkgPrivate, argv, cb) {
146117
var tagOption
147118
if (argv.sign) {
148119
tagOption = '-s '
149120
} else {
150121
tagOption = '-a '
151122
}
152-
checkpoint('tagging release %s', [newVersion])
153-
exec('git tag ' + tagOption + 'v' + newVersion + ' -m "' + formatCommitMessage(argv.message, newVersion) + '"', function (err, stdout, stderr) {
154-
handleExecError(err, stderr)
123+
checkpoint(argv, 'tagging release %s', [newVersion])
124+
handledExec(argv, 'git tag ' + tagOption + 'v' + newVersion + ' -m "' + formatCommitMessage(argv.message, newVersion) + '"', cb, function () {
155125
var message = 'git push --follow-tags origin master'
156-
if (pkg.private !== true) message += '; npm publish'
126+
if (pkgPrivate !== true) message += '; npm publish'
157127

158-
checkpoint('Run `%s` to publish', [message], chalk.blue(figures.info))
128+
checkpoint(argv, 'Run `%s` to publish', [message], chalk.blue(figures.info))
129+
cb()
159130
})
160131
}
161132

@@ -164,15 +135,24 @@ function createIfMissing (argv) {
164135
accessSync(argv.infile, fs.F_OK)
165136
} catch (err) {
166137
if (err.code === 'ENOENT') {
167-
checkpoint('created %s', [argv.infile])
138+
checkpoint(argv, 'created %s', [argv.infile])
168139
argv.outputUnreleased = true
169140
fs.writeFileSync(argv.infile, '\n', 'utf-8')
170141
}
171142
}
172143
}
173144

174-
function checkpoint (msg, args, figure) {
145+
function checkpoint (argv, msg, args, figure) {
175146
console.info((figure || chalk.green(figures.tick)) + ' ' + util.format.apply(util, [msg].concat(args.map(function (arg) {
176147
return chalk.bold(arg)
177148
}))))
178-
};
149+
}
150+
151+
function printError (argv, msg, opts) {
152+
opts = objectAssign({
153+
level: 'error',
154+
color: 'red'
155+
}, opts)
156+
157+
console[opts.level](chalk[opts.color](msg))
158+
}

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "standard-version",
33
"version": "2.4.0",
44
"description": "replacement for `npm version` with automatic CHANGELOG generation",
5-
"bin": "index.js",
5+
"bin": "cli.js",
66
"scripts": {
77
"pretest": "standard",
88
"coverage": "nyc report --reporter=text-lcov | coveralls",
@@ -37,6 +37,7 @@
3737
"conventional-recommended-bump": "^0.3.0",
3838
"figures": "^1.5.0",
3939
"fs-access": "^1.0.0",
40+
"object-assign": "^4.1.0",
4041
"semver": "^5.1.0",
4142
"yargs": "^5.0.0"
4243
},

0 commit comments

Comments
 (0)