Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/npm/cli.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJames Talmage <james@talmage.io>2015-06-25 05:20:29 +0300
committerRebecca Turner <me@re-becca.org>2015-07-01 12:41:34 +0300
commit21ce8fe7dfda78b5522963dc2caff080853da5e7 (patch)
tree09d59f60cdafde88de165516ba44c7b845c383be
parent311db70fa3f0a6afd89ce24e5b02ff86dc1c8bb8 (diff)
version: allow scripts to add files to the commit
Closes #8620 (cherry picked from commit a2106bf)
-rw-r--r--doc/cli/npm-version.md39
-rw-r--r--lib/version.js58
-rw-r--r--test/tap/version-git-not-clean.js38
-rw-r--r--test/tap/version-lifecycle.js103
4 files changed, 198 insertions, 40 deletions
diff --git a/doc/cli/npm-version.md b/doc/cli/npm-version.md
index 21295027f..0527a4d71 100644
--- a/doc/cli/npm-version.md
+++ b/doc/cli/npm-version.md
@@ -19,10 +19,11 @@ valid second argument to semver.inc (one of `patch`, `minor`, `major`,
`prepatch`, `preminor`, `premajor`, `prerelease`). In the second case,
the existing version will be incremented by 1 in the specified field.
-If run in a git repo, it will also create a version commit and tag, and fail if
-the repo is not clean. This behavior is controlled by `git-tag-version` (see
-below), and can be disabled on the command line by running `npm
---no-git-tag-version version`
+If run in a git repo, it will also create a version commit and tag.
+This behavior is controlled by `git-tag-version` (see below), and can
+be disabled on the command line by running `npm --no-git-tag-version version`.
+It will fail if the working directory is not clean, unless the `--force`
+flag is set.
If supplied with `--message` (shorthand: `-m`) config option, npm will
use it as a commit message when creating a version commit. If the
@@ -46,11 +47,33 @@ in your git config for this to work properly. For example:
If `preversion`, `version`, or `postversion` are in the `scripts` property of
the package.json, they will be executed as part of running `npm version`.
-`preversion` and `version` are executed before bumping the package version, and
-`postversion` is executed afterwards. For example, to run `npm version` only if
-all tests pass:
- "scripts": { "preversion": "npm test" }
+The exact order of execution is as follows:
+ 1. Check to make sure the git working directory is clean before we get started.
+ Your scripts may add files to the commit in future steps.
+ This step is skipped if the `--force` flag is set.
+ 2. Run the `preversion` script. These scripts have access to the old `version` in package.json.
+ A typical use would be running your full test suite before deploying.
+ Any files you want added to the commit should be explicitly added using `git add`.
+ 3. Bump `version` in `package.json` as requested (`patch`, `minor`, `major`, etc).
+ 4. Run the `version` script. These scripts have access to the new `version` in package.json
+ (so they can incorporate it into file headers in generated files for example).
+ Again, scripts should explicitly add generated files to the commit using `git add`.
+ 5. Commit and tag.
+ 6. Run the `postversion` script. Use it to clean up the file system or automatically push
+ the commit and/or tag.
+
+Take the following example:
+
+ "scripts": {
+ "preversion": "npm test",
+ "version": "npm run build && git add -A dist",
+ "postversion": "git push && git push --tags && rm -rf build/temp"
+ }
+
+This runs all your tests, and proceeds only if they pass. Then runs your `build` script, and
+adds everything in the `dist` directory to the commit. After the commit, it pushes the new commit
+and tag up to the server, and deletes the `build/temp` directory.
## CONFIGURATION
diff --git a/lib/version.js b/lib/version.js
index c93e3b850..b33392488 100644
--- a/lib/version.js
+++ b/lib/version.js
@@ -52,34 +52,52 @@ function version (args, silent, cb_) {
data.version = newVersion
var lifecycleData = Object.create(data)
lifecycleData._id = data.name + '@' + newVersion
+ var localData = {}
var where = npm.prefix
chain([
- [lifecycle, lifecycleData, 'preversion', where],
- [version_, data, silent],
- [lifecycle, lifecycleData, 'version', where],
- [lifecycle, lifecycleData, 'postversion', where] ],
- cb_)
+ [checkGit, localData],
+ [lifecycle, lifecycleData, 'preversion', where],
+ [updatePackage, newVersion, silent],
+ [lifecycle, lifecycleData, 'version', where],
+ [commit, localData, newVersion],
+ [lifecycle, lifecycleData, 'postversion', where] ],
+ cb_)
})
}
-function version_ (data, silent, cb_) {
+function readPackage (cb) {
+ var packagePath = path.join(npm.localPrefix, 'package.json')
+ fs.readFile(packagePath, function (er, data) {
+ if (er) return cb(new Error(er))
+ if (data) data = data.toString()
+ try {
+ data = JSON.parse(data)
+ } catch (e) {
+ er = e
+ data = null
+ }
+ cb(er, data)
+ })
+}
+
+function updatePackage (newVersion, silent, cb_) {
function cb (er) {
- if (!er && !silent) console.log('v' + data.version)
+ if (!er && !silent) console.log('v' + newVersion)
cb_(er)
}
- checkGit(function (er, hasGit) {
+ readPackage(function (er, data) {
if (er) return cb(new Error(er))
+ data.version = newVersion
+ write(data, 'package.json', cb)
+ })
+}
- write(data, 'package.json', function (er) {
- if (er) return cb(new Error(er))
-
- updateShrinkwrap(data.version, function (er, hasShrinkwrap) {
- if (er || !hasGit) return cb(er)
- commit(data.version, hasShrinkwrap, cb)
- })
- })
+function commit (localData, newVersion, cb) {
+ updateShrinkwrap(newVersion, function (er, hasShrinkwrap) {
+ if (er || !localData.hasGit) return cb(er)
+ _commit(newVersion, hasShrinkwrap, cb)
})
}
@@ -121,7 +139,7 @@ function dump (data, cb) {
cb()
}
-function checkGit (cb) {
+function checkGit (localData, cb) {
fs.stat(path.join(npm.localPrefix, '.git'), function (er, s) {
var doGit = !er && s.isDirectory() && npm.config.get('git-tag-version')
if (!doGit) {
@@ -149,19 +167,19 @@ function checkGit (cb) {
}).map(function (line) {
return line.trim()
})
- if (lines.length) {
+ if (lines.length && !npm.config.get('force')) {
return cb(new Error(
'Git working directory not clean.\n' + lines.join('\n')
))
}
-
+ localData.hasGit = true
cb(null, true)
}
)
})
}
-function commit (version, hasShrinkwrap, cb) {
+function _commit (version, hasShrinkwrap, cb) {
var options = { env: process.env }
var message = npm.config.get('message').replace(/%s/g, version)
var sign = npm.config.get('sign-git-tag')
diff --git a/test/tap/version-git-not-clean.js b/test/tap/version-git-not-clean.js
index d770a86e6..a942be3e8 100644
--- a/test/tap/version-git-not-clean.js
+++ b/test/tap/version-git-not-clean.js
@@ -18,17 +18,6 @@ test('npm version <semver> with working directory not clean', function (t) {
which('git', function (err, git) {
t.ifError(err, 'git found')
- function gitInit (_cb) {
- var child = spawn(git, ['init'])
- var out = ''
- child.stdout.on('data', function (d) {
- out += d.toString()
- })
- child.on('exit', function () {
- return _cb(out)
- })
- }
-
function addPackageJSON (_cb) {
var data = JSON.stringify({ name: 'blah', version: '0.1.2' })
fs.writeFile('package.json', data, function () {
@@ -46,7 +35,7 @@ test('npm version <semver> with working directory not clean', function (t) {
})
}
- gitInit(function () {
+ common.makeGitRepo({path: pkg}, function () {
addPackageJSON(function () {
var data = JSON.stringify({ name: 'blah', version: '0.1.3' })
fs.writeFile('package.json', data, function () {
@@ -66,6 +55,31 @@ test('npm version <semver> with working directory not clean', function (t) {
})
})
+test('npm version <semver> --force with working directory not clean', function (t) {
+ npm.load({ cache: cache, registry: common.registry, prefix: pkg }, function () {
+ common.npm(
+ [
+ '--force',
+ 'version',
+ 'patch'
+ ],
+ {cwd: pkg, env: {PATH: process.env.PATH}},
+ function (err, code, stdout, stderr) {
+ t.ifError(err, 'npm version ran without issue')
+ t.notOk(code, 'exited with a non-error code')
+ var errorLines = stderr.trim().split('\n')
+ .map(function (line) {
+ return line.trim()
+ })
+ .filter(function (line) {
+ return !line.indexOf('using --force')
+ })
+ t.notOk(errorLines.length, 'no error output')
+ t.end()
+ })
+ })
+})
+
test('cleanup', function (t) {
// windows fix for locked files
process.chdir(osenv.tmpdir())
diff --git a/test/tap/version-lifecycle.js b/test/tap/version-lifecycle.js
index da0af1086..7cf719c4f 100644
--- a/test/tap/version-lifecycle.js
+++ b/test/tap/version-lifecycle.js
@@ -10,6 +10,8 @@ var common = require('../common-tap.js')
var npm = require('../../')
var pkg = path.resolve(__dirname, 'version-lifecycle')
var cache = path.resolve(pkg, 'cache')
+var npmrc = path.resolve(pkg, './.npmrc')
+var configContents = 'sign-git-tag=false\n'
test('npm version <semver> with failing preversion lifecycle script', function (t) {
setup()
@@ -34,6 +36,29 @@ test('npm version <semver> with failing preversion lifecycle script', function (
})
})
+test('npm version <semver> with failing version lifecycle script', function (t) {
+ setup()
+ fs.writeFileSync(path.resolve(pkg, 'package.json'), JSON.stringify({
+ author: 'Alex Wolfe',
+ name: 'version-lifecycle',
+ version: '0.0.0',
+ description: 'Test for npm version if postversion script fails',
+ scripts: {
+ version: './fail.sh'
+ }
+ }), 'utf8')
+ fs.writeFileSync(path.resolve(pkg, 'fail.sh'), 'exit 50', 'utf8')
+ fs.chmodSync(path.resolve(pkg, 'fail.sh'), 448)
+ npm.load({cache: cache, registry: common.registry}, function () {
+ var version = require('../../lib/version')
+ version(['patch'], function (err) {
+ t.ok(err)
+ t.ok(err.message.match(/Exit status 50/))
+ t.end()
+ })
+ })
+})
+
test('npm version <semver> with failing postversion lifecycle script', function (t) {
setup()
fs.writeFileSync(path.resolve(pkg, 'package.json'), JSON.stringify({
@@ -57,6 +82,52 @@ test('npm version <semver> with failing postversion lifecycle script', function
})
})
+test('npm version <semver> execution order', function (t) {
+ setup()
+ fs.writeFileSync(path.resolve(pkg, 'package.json'), JSON.stringify({
+ author: 'Alex Wolfe',
+ name: 'version-lifecycle',
+ version: '0.0.0',
+ description: 'Test for npm version if postversion script fails',
+ scripts: {
+ preversion: './preversion.sh',
+ version: './version.sh',
+ postversion: './postversion.sh'
+ }
+ }), 'utf8')
+ makeScript('preversion')
+ makeScript('version')
+ makeScript('postversion')
+ npm.load({cache: cache, registry: common.registry}, function () {
+ common.makeGitRepo({path: pkg}, function (err, git) {
+ t.ifError(err, 'git bootstrap ran without error')
+
+ var version = require('../../lib/version')
+ version(['patch'], function (err) {
+ t.ifError(err, 'version command complete')
+
+ t.equal('0.0.0', readPackage('preversion').version, 'preversion')
+ t.deepEqual(readStatus('preversion', t), {
+ 'preversion-package.json': 'A'
+ })
+
+ t.equal('0.0.1', readPackage('version').version, 'version')
+ t.deepEqual(readStatus('version', t), {
+ 'package.json': 'M',
+ 'preversion-package.json': 'A',
+ 'version-package.json': 'A'
+ })
+
+ t.equal('0.0.1', readPackage('postversion').version, 'postversion')
+ t.deepEqual(readStatus('postversion', t), {
+ 'postversion-package.json': 'A'
+ })
+ t.end()
+ })
+ })
+ })
+})
+
test('cleanup', function (t) {
process.chdir(osenv.tmpdir())
rimraf.sync(pkg)
@@ -67,5 +138,37 @@ function setup () {
mkdirp.sync(pkg)
mkdirp.sync(path.join(pkg, 'node_modules'))
mkdirp.sync(cache)
+ fs.writeFileSync(npmrc, configContents, 'ascii')
process.chdir(pkg)
}
+
+function makeScript (lifecycle) {
+ var contents = [
+ 'cp package.json ' + lifecycle + '-package.json',
+ 'git add ' + lifecycle + '-package.json',
+ 'git status --porcelain > ' + lifecycle + '-git.txt'
+ ].join('\n')
+ var scriptPath = path.join(pkg, lifecycle + '.sh')
+ fs.writeFileSync(scriptPath, contents, 'utf-8')
+ fs.chmodSync(scriptPath, 448)
+}
+
+function readPackage (lifecycle) {
+ return JSON.parse(fs.readFileSync(path.join(pkg, lifecycle + '-package.json'), 'utf-8'))
+}
+
+function readStatus (lifecycle, t) {
+ var status = {}
+ fs.readFileSync(path.join(pkg, lifecycle + '-git.txt'), 'utf-8')
+ .trim()
+ .split('\n')
+ .forEach(function (line) {
+ line = line.trim()
+ if (line && !line.match(/^\?\? /)) {
+ var parts = line.split(/\s+/)
+ t.equal(parts.length, 2, lifecycle + ' : git status has too many words : ' + line)
+ status[parts[1].trim()] = parts[0].trim()
+ }
+ })
+ return status
+}