Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for skipping lifecycle steps, polish lifecycle work #188

Merged
merged 4 commits into from
Jun 6, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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