Skip to content

Commit dfd1d12

Browse files
authored
feat: add prebump, postbump, precommit, lifecycle scripts (#186)
1 parent 86af7fc commit dfd1d12

13 files changed

+374
-169
lines changed

.travis.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ language: node_js
22
node_js:
33
- "4"
44
- "5"
5-
- "node"
5+
- "stable"
66
before_script:
77
- git config --global user.name 'Travis-CI'
88
- git config --global user.email '[email protected]'

README.md

+27
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,33 @@ standard-version --no-verify
152152

153153
If you have your GPG key set up, add the `--sign` or `-s` flag to your `standard-version` command.
154154

155+
### Lifecycle scripts
156+
157+
`standard-version` supports lifecycle scripts. These allow you to execute your
158+
own supplementary commands during the release. The following
159+
hooks are available:
160+
161+
* `prebump`: executed before the version bump is calculated. If the `prebump`
162+
script returns a version #, it will be used rather than
163+
the version calculated by `standard-version`.
164+
* `postbump`: executed after the version has been bumped and written to
165+
package.json. The flag `--new-version` is populated with the version that is
166+
being released.
167+
* `precommit`: called after CHANGELOG.md and package.json have been updated,
168+
but before changes have been committed to git.
169+
170+
Simply add the following to your package.json, to enable lifecycle scripts:
171+
172+
```json
173+
{
174+
"standard-version": {
175+
"scripts": {
176+
"prebump": "echo 9.9.9"
177+
}
178+
}
179+
}
180+
```
181+
155182
### Committing generated artifacts in the release commit
156183

157184
If you want to commit generated artifacts in the release commit (e.g. [#96](https://github.com/conventional-changelog/standard-version/issues/96)), you can use the `--commit-all` or `-a` flag. You will need to stage the artifacts you want to commit, so your `release` command could look like this:

appveyor.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
environment:
22
matrix:
33
- nodejs_version: '6'
4-
- nodejs_version: '5'
5-
- nodejs_version: '4'
64
install:
75
- ps: Install-Product node $env:nodejs_version
6+
- git config --global user.name 'Appveyor'
7+
- git config --global user.email '[email protected]'
88
- set CI=true
99
- npm -g install npm@latest
1010
- set PATH=%APPDATA%\npm;%PATH%

bin/cli.js

+3-4
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,8 @@ var cmdParser = require('../command')
66
if (process.version.match(/v(\d+)\./)[1] < 4) {
77
console.error('standard-version: Node v4 or greater is required. `standard-version` did not run.')
88
} else {
9-
standardVersion(cmdParser.argv, function (err) {
10-
if (err) {
9+
standardVersion(cmdParser.argv)
10+
.catch(() => {
1111
process.exit(1)
12-
}
13-
})
12+
})
1413
}

command.js

+12
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,22 @@ module.exports = require('yargs')
6969
default: defaults.tagPrefix,
7070
global: true
7171
})
72+
.option('scripts', {
73+
describe: 'Scripts to execute for lifecycle events (prebump, precommit, etc.,)',
74+
default: {}
75+
})
76+
.check((argv) => {
77+
if (typeof argv.scripts !== 'object' || Array.isArray(argv.scripts)) {
78+
throw Error('hooks must be an object')
79+
} else {
80+
return true
81+
}
82+
})
7283
.version()
7384
.alias('version', 'v')
7485
.help()
7586
.alias('help', 'h')
7687
.example('$0', 'Update changelog and tag release')
7788
.example('$0 -m "%s: see changelog for details"', 'Update changelog and tag release with custom commit message')
89+
.pkgConf('standard-version')
7890
.wrap(97)

index.js

+100-120
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,60 @@
1-
var conventionalRecommendedBump = require('conventional-recommended-bump')
2-
var conventionalChangelog = require('conventional-changelog')
3-
var path = require('path')
4-
5-
var chalk = require('chalk')
6-
var figures = require('figures')
7-
var exec = require('child_process').exec
8-
var fs = require('fs')
9-
var accessSync = require('fs-access').sync
10-
var semver = require('semver')
11-
var util = require('util')
12-
var objectAssign = require('object-assign')
13-
14-
module.exports = function standardVersion (argv, done) {
1+
const conventionalRecommendedBump = require('conventional-recommended-bump')
2+
const conventionalChangelog = require('conventional-changelog')
3+
const path = require('path')
4+
5+
const chalk = require('chalk')
6+
const figures = require('figures')
7+
const fs = require('fs')
8+
const accessSync = require('fs-access').sync
9+
const semver = require('semver')
10+
const util = require('util')
11+
12+
const checkpoint = require('./lib/checkpoint')
13+
const printError = require('./lib/print-error')
14+
const runExec = require('./lib/run-exec')
15+
const runLifecycleScript = require('./lib/run-lifecycle-script')
16+
17+
module.exports = function standardVersion (argv) {
1518
var pkgPath = path.resolve(process.cwd(), './package.json')
1619
var pkg = require(pkgPath)
20+
var newVersion = pkg.version
21+
var scripts = argv.scripts || {}
1722
var defaults = require('./defaults')
18-
var args = objectAssign({}, defaults, argv)
23+
var args = Object.assign({}, defaults, argv)
1924

20-
bumpVersion(args.releaseAs, function (err, release) {
21-
if (err) {
22-
printError(args, err.message)
23-
return done(err)
24-
}
25-
26-
var newVersion = pkg.version
27-
28-
if (!args.firstRelease) {
29-
var releaseType = getReleaseType(args.prerelease, release.releaseType, pkg.version)
30-
newVersion = semver.valid(releaseType) || semver.inc(pkg.version, releaseType, args.prerelease)
31-
updateConfigs(args, newVersion)
32-
} else {
33-
checkpoint(args, 'skip version bump on first release', [], chalk.red(figures.cross))
34-
}
35-
36-
outputChangelog(args, function (err) {
37-
if (err) {
38-
return done(err)
25+
return runLifecycleScript(args, 'prebump', null, scripts)
26+
.then((stdout) => {
27+
if (stdout && stdout.trim().length) args.releaseAs = stdout.trim()
28+
return bumpVersion(args.releaseAs)
29+
})
30+
.then((release) => {
31+
if (!args.firstRelease) {
32+
var releaseType = getReleaseType(args.prerelease, release.releaseType, pkg.version)
33+
newVersion = semver.valid(releaseType) || semver.inc(pkg.version, releaseType, args.prerelease)
34+
updateConfigs(args, newVersion)
35+
} else {
36+
checkpoint(args, 'skip version bump on first release', [], chalk.red(figures.cross))
3937
}
40-
commit(args, newVersion, function (err) {
41-
if (err) {
42-
return done(err)
43-
}
44-
return tag(newVersion, pkg.private, args, done)
45-
})
38+
39+
return runLifecycleScript(args, 'postbump', newVersion, scripts)
40+
})
41+
.then(() => {
42+
return outputChangelog(args)
43+
})
44+
.then(() => {
45+
return runLifecycleScript(args, 'precommit', newVersion, scripts)
46+
})
47+
.then((message) => {
48+
if (message && message.length) args.message = message
49+
return commit(args, newVersion)
50+
})
51+
.then(() => {
52+
return tag(newVersion, pkg.private, args)
53+
})
54+
.catch((err) => {
55+
printError(args, err.message)
56+
throw err
4657
})
47-
})
4858
}
4959

5060
/**
@@ -145,62 +155,52 @@ function getTypePriority (type) {
145155
}
146156

147157
function bumpVersion (releaseAs, callback) {
148-
if (releaseAs) {
149-
callback(null, {
150-
releaseType: releaseAs
151-
})
152-
} else {
153-
conventionalRecommendedBump({
154-
preset: 'angular'
155-
}, function (err, release) {
156-
callback(err, release)
157-
})
158-
}
159-
}
160-
161-
function outputChangelog (argv, cb) {
162-
createIfMissing(argv)
163-
var 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'
164-
var oldContent = fs.readFileSync(argv.infile, 'utf-8')
165-
// find the position of the last release and remove header:
166-
if (oldContent.indexOf('<a name=') !== -1) {
167-
oldContent = oldContent.substring(oldContent.indexOf('<a name='))
168-
}
169-
var content = ''
170-
var changelogStream = conventionalChangelog({
171-
preset: 'angular'
172-
}, undefined, {merges: null})
173-
.on('error', function (err) {
174-
return cb(err)
175-
})
176-
177-
changelogStream.on('data', function (buffer) {
178-
content += buffer.toString()
179-
})
180-
181-
changelogStream.on('end', function () {
182-
checkpoint(argv, 'outputting changes to %s', [argv.infile])
183-
fs.writeFileSync(argv.infile, header + '\n' + (content + oldContent).replace(/\n+$/, '\n'), 'utf-8')
184-
return cb()
158+
return new Promise((resolve, reject) => {
159+
if (releaseAs) {
160+
return resolve({
161+
releaseType: releaseAs
162+
})
163+
} else {
164+
conventionalRecommendedBump({
165+
preset: 'angular'
166+
}, function (err, release) {
167+
if (err) return reject(err)
168+
else return resolve(release)
169+
})
170+
}
185171
})
186172
}
187173

188-
function handledExec (argv, cmd, errorCb, successCb) {
189-
// Exec given cmd and handle possible errors
190-
exec(cmd, function (err, stdout, stderr) {
191-
// If exec returns content in stderr, but no error, print it as a warning
192-
// If exec returns an error, print it and exit with return code 1
193-
if (err) {
194-
printError(argv, stderr || err.message)
195-
return errorCb(err)
196-
} else if (stderr) {
197-
printError(argv, stderr, {level: 'warn', color: 'yellow'})
174+
function outputChangelog (argv) {
175+
return new Promise((resolve, reject) => {
176+
createIfMissing(argv)
177+
var 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'
178+
var oldContent = fs.readFileSync(argv.infile, 'utf-8')
179+
// find the position of the last release and remove header:
180+
if (oldContent.indexOf('<a name=') !== -1) {
181+
oldContent = oldContent.substring(oldContent.indexOf('<a name='))
198182
}
199-
successCb()
183+
var content = ''
184+
var changelogStream = conventionalChangelog({
185+
preset: 'angular'
186+
}, undefined, {merges: null})
187+
.on('error', function (err) {
188+
return reject(err)
189+
})
190+
191+
changelogStream.on('data', function (buffer) {
192+
content += buffer.toString()
193+
})
194+
195+
changelogStream.on('end', function () {
196+
checkpoint(argv, 'outputting changes to %s', [argv.infile])
197+
fs.writeFileSync(argv.infile, header + '\n' + (content + oldContent).replace(/\n+$/, '\n'), 'utf-8')
198+
return resolve()
199+
})
200200
})
201201
}
202202

203-
function commit (argv, newVersion, cb) {
203+
function commit (argv, newVersion) {
204204
var msg = 'committing %s'
205205
var args = [argv.infile]
206206
var verify = argv.verify === false || argv.n ? '--no-verify ' : ''
@@ -215,32 +215,31 @@ function commit (argv, newVersion, cb) {
215215
}
216216
})
217217
checkpoint(argv, msg, args)
218-
handledExec(argv, 'git add' + toAdd + ' ' + argv.infile, cb, function () {
219-
handledExec(argv, 'git commit ' + verify + (argv.sign ? '-S ' : '') + (argv.commitAll ? '' : (argv.infile + toAdd)) + ' -m "' + formatCommitMessage(argv.message, newVersion) + '"', cb, function () {
220-
cb()
218+
return runExec(argv, 'git add' + toAdd + ' ' + argv.infile)
219+
.then(() => {
220+
return runExec(argv, 'git commit ' + verify + (argv.sign ? '-S ' : '') + (argv.commitAll ? '' : (argv.infile + toAdd)) + ' -m "' + formatCommitMessage(argv.message, newVersion) + '"')
221221
})
222-
})
223222
}
224223

225224
function formatCommitMessage (msg, newVersion) {
226225
return String(msg).indexOf('%s') !== -1 ? util.format(msg, newVersion) : msg
227226
}
228227

229-
function tag (newVersion, pkgPrivate, argv, cb) {
228+
function tag (newVersion, pkgPrivate, argv) {
230229
var tagOption
231230
if (argv.sign) {
232231
tagOption = '-s '
233232
} else {
234233
tagOption = '-a '
235234
}
236235
checkpoint(argv, 'tagging release %s', [newVersion])
237-
handledExec(argv, 'git tag ' + tagOption + argv.tagPrefix + newVersion + ' -m "' + formatCommitMessage(argv.message, newVersion) + '"', cb, function () {
238-
var message = 'git push --follow-tags origin master'
239-
if (pkgPrivate !== true) message += '; npm publish'
236+
return runExec(argv, 'git tag ' + tagOption + argv.tagPrefix + newVersion + ' -m "' + formatCommitMessage(argv.message, newVersion) + '"')
237+
.then(() => {
238+
var message = 'git push --follow-tags origin master'
239+
if (pkgPrivate !== true) message += '; npm publish'
240240

241-
checkpoint(argv, 'Run `%s` to publish', [message], chalk.blue(figures.info))
242-
cb()
243-
})
241+
checkpoint(argv, 'Run `%s` to publish', [message], chalk.blue(figures.info))
242+
})
244243
}
245244

246245
function createIfMissing (argv) {
@@ -254,22 +253,3 @@ function createIfMissing (argv) {
254253
}
255254
}
256255
}
257-
258-
function checkpoint (argv, msg, args, figure) {
259-
if (!argv.silent) {
260-
console.info((figure || chalk.green(figures.tick)) + ' ' + util.format.apply(util, [msg].concat(args.map(function (arg) {
261-
return chalk.bold(arg)
262-
}))))
263-
}
264-
}
265-
266-
function printError (argv, msg, opts) {
267-
if (!argv.silent) {
268-
opts = objectAssign({
269-
level: 'error',
270-
color: 'red'
271-
}, opts)
272-
273-
console[opts.level](chalk[opts.color](msg))
274-
}
275-
}

lib/checkpoint.js

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
const chalk = require('chalk')
2+
const figures = require('figures')
3+
const util = require('util')
4+
5+
module.exports = function (argv, msg, args, figure) {
6+
if (!argv.silent) {
7+
console.info((figure || chalk.green(figures.tick)) + ' ' + util.format.apply(util, [msg].concat(args.map(function (arg) {
8+
return chalk.bold(arg)
9+
}))))
10+
}
11+
}

lib/print-error.js

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
const chalk = require('chalk')
2+
3+
module.exports = function (argv, msg, opts) {
4+
if (!argv.silent) {
5+
opts = Object.assign({
6+
level: 'error',
7+
color: 'red'
8+
}, opts)
9+
10+
console[opts.level](chalk[opts.color](msg))
11+
}
12+
}

0 commit comments

Comments
 (0)