Skip to content

Commit d31dcdb

Browse files
authored
feat: add support for skipping lifecycle steps, polish lifecycle work (#188)
1 parent d073353 commit d31dcdb

File tree

12 files changed

+377
-249
lines changed

12 files changed

+377
-249
lines changed

README.md

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -156,18 +156,16 @@ If you have your GPG key set up, add the `--sign` or `-s` flag to your `standard
156156

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

161-
* `prebump`: executed before the version bump is calculated. If the `prebump`
161+
* `prebump`/`postbump`: executed before and after the version is bumped. If the `prebump`
162162
script returns a version #, it will be used rather than
163163
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.
164+
* `prechangelog`/`postchangelog`: executes before and after the CHANGELOG is generated.
165+
* `precommit`/`postcommit`: called before and after the commit step.
166+
* `pretag`/`posttag`: called before and after the tagging step.
169167

170-
Simply add the following to your package.json, to enable lifecycle scripts:
168+
Simply add the following to your package.json to configure lifecycle scripts:
171169

172170
```json
173171
{
@@ -179,6 +177,21 @@ Simply add the following to your package.json, to enable lifecycle scripts:
179177
}
180178
```
181179

180+
### Skipping lifecycle steps
181+
182+
You can skip any of the lifecycle steps (`bump`, `changelog`, `commit`, `tag`),
183+
by adding the following to your package.json:
184+
185+
```json
186+
{
187+
"standard-version": {
188+
"skip": {
189+
"changelog": true
190+
}
191+
}
192+
}
193+
```
194+
182195
### Committing generated artifacts in the release commit
183196

184197
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:

command.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,11 @@ module.exports = require('yargs')
6060
default: defaults.tagPrefix
6161
})
6262
.option('scripts', {
63-
describe: 'Scripts to execute for lifecycle events (prebump, precommit, etc.,)',
63+
describe: 'Provide scripts to execute for lifecycle events (prebump, precommit, etc.,)',
64+
default: defaults.scripts
65+
})
66+
.option('skip', {
67+
describe: 'Map of steps in the release process that should be skipped',
6468
default: defaults.scripts
6569
})
6670
.option('dry-run', {
@@ -71,6 +75,8 @@ module.exports = require('yargs')
7175
.check((argv) => {
7276
if (typeof argv.scripts !== 'object' || Array.isArray(argv.scripts)) {
7377
throw Error('scripts must be an object')
78+
} else if (typeof argv.skip !== 'object' || Array.isArray(argv.skip)) {
79+
throw Error('skip must be an object')
7480
} else {
7581
return true
7682
}

defaults.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@
88
"silent": false,
99
"tagPrefix": "v",
1010
"scripts": {},
11+
"skip": {},
1112
"dryRun": false
1213
}

index.js

Lines changed: 13 additions & 235 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,10 @@
1-
const conventionalRecommendedBump = require('conventional-recommended-bump')
2-
const conventionalChangelog = require('conventional-changelog')
31
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')
132
const printError = require('./lib/print-error')
14-
const runExec = require('./lib/run-exec')
15-
const runLifecycleScript = require('./lib/run-lifecycle-script')
16-
const writeFile = require('./lib/write-file')
3+
4+
const bump = require('./lib/lifecycles/bump')
5+
const changelog = require('./lib/lifecycles/changelog')
6+
const commit = require('./lib/lifecycles/commit')
7+
const tag = require('./lib/lifecycles/tag')
178

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

25-
return runLifecycleScript(args, 'prebump', null)
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))
37-
}
38-
39-
return runLifecycleScript(args, 'postbump', newVersion, args)
40-
})
16+
return Promise.resolve()
4117
.then(() => {
42-
return outputChangelog(args, newVersion)
18+
return bump(args, pkg)
4319
})
44-
.then(() => {
45-
return runLifecycleScript(args, 'precommit', newVersion, args)
20+
.then((_newVersion) => {
21+
// if bump runs, it calculaes the new version that we
22+
// should release at.
23+
if (_newVersion) newVersion = _newVersion
24+
return changelog(args, newVersion)
4625
})
47-
.then((message) => {
48-
if (message && message.length) args.message = message
26+
.then(() => {
4927
return commit(args, newVersion)
5028
})
5129
.then(() => {
@@ -56,203 +34,3 @@ module.exports = function standardVersion (argv) {
5634
throw err
5735
})
5836
}
59-
60-
/**
61-
* attempt to update the version # in a collection of common config
62-
* files, e.g., package.json, bower.json.
63-
*
64-
* @param args config object
65-
* @param newVersion version # to update to.
66-
* @return {string}
67-
*/
68-
var configsToUpdate = {}
69-
function updateConfigs (args, newVersion) {
70-
configsToUpdate[path.resolve(process.cwd(), './package.json')] = false
71-
configsToUpdate[path.resolve(process.cwd(), './npm-shrinkwrap.json')] = false
72-
configsToUpdate[path.resolve(process.cwd(), './bower.json')] = false
73-
Object.keys(configsToUpdate).forEach(function (configPath) {
74-
try {
75-
var stat = fs.lstatSync(configPath)
76-
if (stat.isFile()) {
77-
var config = require(configPath)
78-
var filename = path.basename(configPath)
79-
checkpoint(args, 'bumping version in ' + filename + ' from %s to %s', [config.version, newVersion])
80-
config.version = newVersion
81-
writeFile(args, configPath, JSON.stringify(config, null, 2) + '\n')
82-
// flag any config files that we modify the version # for
83-
// as having been updated.
84-
configsToUpdate[configPath] = true
85-
}
86-
} catch (err) {
87-
if (err.code !== 'ENOENT') console.warn(err.message)
88-
}
89-
})
90-
}
91-
92-
function getReleaseType (prerelease, expectedReleaseType, currentVersion) {
93-
if (isString(prerelease)) {
94-
if (isInPrerelease(currentVersion)) {
95-
if (shouldContinuePrerelease(currentVersion, expectedReleaseType) ||
96-
getTypePriority(getCurrentActiveType(currentVersion)) > getTypePriority(expectedReleaseType)
97-
) {
98-
return 'prerelease'
99-
}
100-
}
101-
102-
return 'pre' + expectedReleaseType
103-
} else {
104-
return expectedReleaseType
105-
}
106-
}
107-
108-
function isString (val) {
109-
return typeof val === 'string'
110-
}
111-
112-
/**
113-
* if a version is currently in pre-release state,
114-
* and if it current in-pre-release type is same as expect type,
115-
* it should continue the pre-release with the same type
116-
*
117-
* @param version
118-
* @param expectType
119-
* @return {boolean}
120-
*/
121-
function shouldContinuePrerelease (version, expectType) {
122-
return getCurrentActiveType(version) === expectType
123-
}
124-
125-
function isInPrerelease (version) {
126-
return Array.isArray(semver.prerelease(version))
127-
}
128-
129-
var TypeList = ['major', 'minor', 'patch'].reverse()
130-
131-
/**
132-
* extract the in-pre-release type in target version
133-
*
134-
* @param version
135-
* @return {string}
136-
*/
137-
function getCurrentActiveType (version) {
138-
var typelist = TypeList
139-
for (var i = 0; i < typelist.length; i++) {
140-
if (semver[typelist[i]](version)) {
141-
return typelist[i]
142-
}
143-
}
144-
}
145-
146-
/**
147-
* calculate the priority of release type,
148-
* major - 2, minor - 1, patch - 0
149-
*
150-
* @param type
151-
* @return {number}
152-
*/
153-
function getTypePriority (type) {
154-
return TypeList.indexOf(type)
155-
}
156-
157-
function bumpVersion (releaseAs, callback) {
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-
}
171-
})
172-
}
173-
174-
function outputChangelog (args, newVersion) {
175-
return new Promise((resolve, reject) => {
176-
createIfMissing(args)
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 = args.dryRun ? '' : fs.readFileSync(args.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='))
182-
}
183-
var content = ''
184-
var context
185-
if (args.dryRun) context = {version: newVersion}
186-
var changelogStream = conventionalChangelog({
187-
preset: 'angular'
188-
}, context, {merges: null})
189-
.on('error', function (err) {
190-
return reject(err)
191-
})
192-
193-
changelogStream.on('data', function (buffer) {
194-
content += buffer.toString()
195-
})
196-
197-
changelogStream.on('end', function () {
198-
checkpoint(args, 'outputting changes to %s', [args.infile])
199-
if (args.dryRun) console.info(`\n---\n${chalk.gray(content.trim())}\n---\n`)
200-
else writeFile(args, args.infile, header + '\n' + (content + oldContent).replace(/\n+$/, '\n'))
201-
return resolve()
202-
})
203-
})
204-
}
205-
206-
function commit (args, newVersion) {
207-
var msg = 'committing %s'
208-
var paths = [args.infile]
209-
var verify = args.verify === false || args.n ? '--no-verify ' : ''
210-
var toAdd = ''
211-
// commit any of the config files that we've updated
212-
// the version # for.
213-
Object.keys(configsToUpdate).forEach(function (p) {
214-
if (configsToUpdate[p]) {
215-
msg += ' and %s'
216-
paths.unshift(path.basename(p))
217-
toAdd += ' ' + path.relative(process.cwd(), p)
218-
}
219-
})
220-
checkpoint(args, msg, paths)
221-
return runExec(args, 'git add' + toAdd + ' ' + args.infile)
222-
.then(() => {
223-
return runExec(args, 'git commit ' + verify + (args.sign ? '-S ' : '') + (args.commitAll ? '' : (args.infile + toAdd)) + ' -m "' + formatCommitMessage(args.message, newVersion) + '"')
224-
})
225-
}
226-
227-
function formatCommitMessage (msg, newVersion) {
228-
return String(msg).indexOf('%s') !== -1 ? util.format(msg, newVersion) : msg
229-
}
230-
231-
function tag (newVersion, pkgPrivate, args) {
232-
var tagOption
233-
if (args.sign) {
234-
tagOption = '-s '
235-
} else {
236-
tagOption = '-a '
237-
}
238-
checkpoint(args, 'tagging release %s', [newVersion])
239-
return runExec(args, 'git tag ' + tagOption + args.tagPrefix + newVersion + ' -m "' + formatCommitMessage(args.message, newVersion) + '"')
240-
.then(() => {
241-
var message = 'git push --follow-tags origin master'
242-
if (pkgPrivate !== true) message += '; npm publish'
243-
244-
checkpoint(args, 'Run `%s` to publish', [message], chalk.blue(figures.info))
245-
})
246-
}
247-
248-
function createIfMissing (args) {
249-
try {
250-
accessSync(args.infile, fs.F_OK)
251-
} catch (err) {
252-
if (err.code === 'ENOENT') {
253-
checkpoint(args, 'created %s', [args.infile])
254-
args.outputUnreleased = true
255-
writeFile(args, args.infile, '\n')
256-
}
257-
}
258-
}

lib/checkpoint.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ const chalk = require('chalk')
22
const figures = require('figures')
33
const util = require('util')
44

5-
module.exports = function (args, msg, vars, figure) {
5+
module.exports = function (argv, msg, args, figure) {
66
const defaultFigure = args.dryRun ? chalk.yellow(figures.tick) : chalk.green(figures.tick)
7-
if (!args.silent) {
8-
console.info((figure || defaultFigure) + ' ' + util.format.apply(util, [msg].concat(vars.map(function (arg) {
7+
if (!argv.silent) {
8+
console.info((figure || defaultFigure) + ' ' + util.format.apply(util, [msg].concat(args.map(function (arg) {
99
return chalk.bold(arg)
1010
}))))
1111
}

lib/format-commit-message.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
const util = require('util')
2+
3+
module.exports = function (msg, newVersion) {
4+
return String(msg).indexOf('%s') !== -1 ? util.format(msg, newVersion) : msg
5+
}

0 commit comments

Comments
 (0)