Skip to content

Commit

Permalink
feat: add support for skipping lifecycle steps, polish lifecycle work (
Browse files Browse the repository at this point in the history
  • Loading branch information
bcoe authored Jun 6, 2017
1 parent d073353 commit d31dcdb
Show file tree
Hide file tree
Showing 12 changed files with 377 additions and 249 deletions.
29 changes: 21 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,18 +156,16 @@ If you have your GPG key set up, add the `--sign` or `-s` flag to your `standard

`standard-version` supports lifecycle scripts. These allow you to execute your
own supplementary commands during the release. The following
hooks are available:
hooks are available and execute in the order documented:

* `prebump`: executed before the version bump is calculated. If the `prebump`
* `prebump`/`postbump`: executed before and after the version is bumped. If the `prebump`
script returns a version #, it will be used rather than
the version calculated by `standard-version`.
* `postbump`: executed after the version has been bumped and written to
package.json. The flag `--new-version` is populated with the version that is
being released.
* `precommit`: called after CHANGELOG.md and package.json have been updated,
but before changes have been committed to git.
* `prechangelog`/`postchangelog`: executes before and after the CHANGELOG is generated.
* `precommit`/`postcommit`: called before and after the commit step.
* `pretag`/`posttag`: called before and after the tagging step.

Simply add the following to your package.json, to enable lifecycle scripts:
Simply add the following to your package.json to configure lifecycle scripts:

```json
{
Expand All @@ -179,6 +177,21 @@ Simply add the following to your package.json, to enable lifecycle scripts:
}
```

### Skipping lifecycle steps

You can skip any of the lifecycle steps (`bump`, `changelog`, `commit`, `tag`),
by adding the following to your package.json:

```json
{
"standard-version": {
"skip": {
"changelog": true
}
}
}
```

### Committing generated artifacts in the release commit

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:
Expand Down
8 changes: 7 additions & 1 deletion command.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,11 @@ module.exports = require('yargs')
default: defaults.tagPrefix
})
.option('scripts', {
describe: 'Scripts to execute for lifecycle events (prebump, precommit, etc.,)',
describe: 'Provide scripts to execute for lifecycle events (prebump, precommit, etc.,)',
default: defaults.scripts
})
.option('skip', {
describe: 'Map of steps in the release process that should be skipped',
default: defaults.scripts
})
.option('dry-run', {
Expand All @@ -71,6 +75,8 @@ module.exports = require('yargs')
.check((argv) => {
if (typeof argv.scripts !== 'object' || Array.isArray(argv.scripts)) {
throw Error('scripts must be an object')
} else if (typeof argv.skip !== 'object' || Array.isArray(argv.skip)) {
throw Error('skip must be an object')
} else {
return true
}
Expand Down
1 change: 1 addition & 0 deletions defaults.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@
"silent": false,
"tagPrefix": "v",
"scripts": {},
"skip": {},
"dryRun": false
}
248 changes: 13 additions & 235 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,10 @@
const conventionalRecommendedBump = require('conventional-recommended-bump')
const conventionalChangelog = require('conventional-changelog')
const path = require('path')

const chalk = require('chalk')
const figures = require('figures')
const fs = require('fs')
const accessSync = require('fs-access').sync
const semver = require('semver')
const util = require('util')

const checkpoint = require('./lib/checkpoint')
const printError = require('./lib/print-error')
const runExec = require('./lib/run-exec')
const runLifecycleScript = require('./lib/run-lifecycle-script')
const writeFile = require('./lib/write-file')

const bump = require('./lib/lifecycles/bump')
const changelog = require('./lib/lifecycles/changelog')
const commit = require('./lib/lifecycles/commit')
const tag = require('./lib/lifecycles/tag')

module.exports = function standardVersion (argv) {
var pkgPath = path.resolve(process.cwd(), './package.json')
Expand All @@ -22,30 +13,17 @@ module.exports = function standardVersion (argv) {
var defaults = require('./defaults')
var args = Object.assign({}, defaults, argv)

return runLifecycleScript(args, 'prebump', null)
.then((stdout) => {
if (stdout && stdout.trim().length) args.releaseAs = stdout.trim()
return bumpVersion(args.releaseAs)
})
.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)
updateConfigs(args, newVersion)
} else {
checkpoint(args, 'skip version bump on first release', [], chalk.red(figures.cross))
}

return runLifecycleScript(args, 'postbump', newVersion, args)
})
return Promise.resolve()
.then(() => {
return outputChangelog(args, newVersion)
return bump(args, pkg)
})
.then(() => {
return runLifecycleScript(args, 'precommit', newVersion, args)
.then((_newVersion) => {
// if bump runs, it calculaes the new version that we
// should release at.
if (_newVersion) newVersion = _newVersion
return changelog(args, newVersion)
})
.then((message) => {
if (message && message.length) args.message = message
.then(() => {
return commit(args, newVersion)
})
.then(() => {
Expand All @@ -56,203 +34,3 @@ module.exports = function standardVersion (argv) {
throw err
})
}

/**
* attempt to update the version # in a collection of common config
* files, e.g., package.json, bower.json.
*
* @param args config object
* @param newVersion version # to update to.
* @return {string}
*/
var configsToUpdate = {}
function updateConfigs (args, newVersion) {
configsToUpdate[path.resolve(process.cwd(), './package.json')] = false
configsToUpdate[path.resolve(process.cwd(), './npm-shrinkwrap.json')] = false
configsToUpdate[path.resolve(process.cwd(), './bower.json')] = false
Object.keys(configsToUpdate).forEach(function (configPath) {
try {
var stat = fs.lstatSync(configPath)
if (stat.isFile()) {
var config = require(configPath)
var filename = path.basename(configPath)
checkpoint(args, 'bumping version in ' + filename + ' from %s to %s', [config.version, newVersion])
config.version = newVersion
writeFile(args, configPath, JSON.stringify(config, null, 2) + '\n')
// flag any config files that we modify the version # for
// as having been updated.
configsToUpdate[configPath] = true
}
} catch (err) {
if (err.code !== 'ENOENT') console.warn(err.message)
}
})
}

function getReleaseType (prerelease, expectedReleaseType, currentVersion) {
if (isString(prerelease)) {
if (isInPrerelease(currentVersion)) {
if (shouldContinuePrerelease(currentVersion, expectedReleaseType) ||
getTypePriority(getCurrentActiveType(currentVersion)) > getTypePriority(expectedReleaseType)
) {
return 'prerelease'
}
}

return 'pre' + expectedReleaseType
} else {
return expectedReleaseType
}
}

function isString (val) {
return typeof val === 'string'
}

/**
* if a version is currently in pre-release state,
* and if it current in-pre-release type is same as expect type,
* it should continue the pre-release with the same type
*
* @param version
* @param expectType
* @return {boolean}
*/
function shouldContinuePrerelease (version, expectType) {
return getCurrentActiveType(version) === expectType
}

function isInPrerelease (version) {
return Array.isArray(semver.prerelease(version))
}

var TypeList = ['major', 'minor', 'patch'].reverse()

/**
* extract the in-pre-release type in target version
*
* @param version
* @return {string}
*/
function getCurrentActiveType (version) {
var typelist = TypeList
for (var i = 0; i < typelist.length; i++) {
if (semver[typelist[i]](version)) {
return typelist[i]
}
}
}

/**
* calculate the priority of release type,
* major - 2, minor - 1, patch - 0
*
* @param type
* @return {number}
*/
function getTypePriority (type) {
return TypeList.indexOf(type)
}

function bumpVersion (releaseAs, callback) {
return new Promise((resolve, reject) => {
if (releaseAs) {
return resolve({
releaseType: releaseAs
})
} else {
conventionalRecommendedBump({
preset: 'angular'
}, function (err, release) {
if (err) return reject(err)
else return resolve(release)
})
}
})
}

function outputChangelog (args, newVersion) {
return new Promise((resolve, reject) => {
createIfMissing(args)
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'
var oldContent = args.dryRun ? '' : fs.readFileSync(args.infile, 'utf-8')
// find the position of the last release and remove header:
if (oldContent.indexOf('<a name=') !== -1) {
oldContent = oldContent.substring(oldContent.indexOf('<a name='))
}
var content = ''
var context
if (args.dryRun) context = {version: newVersion}
var changelogStream = conventionalChangelog({
preset: 'angular'
}, context, {merges: null})
.on('error', function (err) {
return reject(err)
})

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

changelogStream.on('end', function () {
checkpoint(args, 'outputting changes to %s', [args.infile])
if (args.dryRun) console.info(`\n---\n${chalk.gray(content.trim())}\n---\n`)
else writeFile(args, args.infile, header + '\n' + (content + oldContent).replace(/\n+$/, '\n'))
return resolve()
})
})
}

function commit (args, newVersion) {
var msg = 'committing %s'
var paths = [args.infile]
var verify = args.verify === false || args.n ? '--no-verify ' : ''
var toAdd = ''
// commit any of the config files that we've updated
// the version # for.
Object.keys(configsToUpdate).forEach(function (p) {
if (configsToUpdate[p]) {
msg += ' and %s'
paths.unshift(path.basename(p))
toAdd += ' ' + path.relative(process.cwd(), p)
}
})
checkpoint(args, msg, paths)
return runExec(args, 'git add' + toAdd + ' ' + args.infile)
.then(() => {
return runExec(args, 'git commit ' + verify + (args.sign ? '-S ' : '') + (args.commitAll ? '' : (args.infile + toAdd)) + ' -m "' + formatCommitMessage(args.message, newVersion) + '"')
})
}

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

function tag (newVersion, pkgPrivate, args) {
var tagOption
if (args.sign) {
tagOption = '-s '
} else {
tagOption = '-a '
}
checkpoint(args, 'tagging release %s', [newVersion])
return runExec(args, 'git tag ' + tagOption + args.tagPrefix + newVersion + ' -m "' + formatCommitMessage(args.message, newVersion) + '"')
.then(() => {
var message = 'git push --follow-tags origin master'
if (pkgPrivate !== true) message += '; npm publish'

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

function createIfMissing (args) {
try {
accessSync(args.infile, fs.F_OK)
} catch (err) {
if (err.code === 'ENOENT') {
checkpoint(args, 'created %s', [args.infile])
args.outputUnreleased = true
writeFile(args, args.infile, '\n')
}
}
}
6 changes: 3 additions & 3 deletions lib/checkpoint.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ const chalk = require('chalk')
const figures = require('figures')
const util = require('util')

module.exports = function (args, msg, vars, figure) {
module.exports = function (argv, msg, args, figure) {
const defaultFigure = args.dryRun ? chalk.yellow(figures.tick) : chalk.green(figures.tick)
if (!args.silent) {
console.info((figure || defaultFigure) + ' ' + util.format.apply(util, [msg].concat(vars.map(function (arg) {
if (!argv.silent) {
console.info((figure || defaultFigure) + ' ' + util.format.apply(util, [msg].concat(args.map(function (arg) {
return chalk.bold(arg)
}))))
}
Expand Down
5 changes: 5 additions & 0 deletions lib/format-commit-message.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const util = require('util')

module.exports = function (msg, newVersion) {
return String(msg).indexOf('%s') !== -1 ? util.format(msg, newVersion) : msg
}
Loading

0 comments on commit d31dcdb

Please sign in to comment.