123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355 |
- 'use strict'
- const BB = require('bluebird')
- const assert = require('assert')
- const chain = require('slide').chain
- const detectIndent = require('detect-indent')
- const detectNewline = require('detect-newline')
- const fs = require('graceful-fs')
- const readFile = BB.promisify(require('graceful-fs').readFile)
- const git = require('./utils/git.js')
- const lifecycle = require('./utils/lifecycle.js')
- const log = require('npmlog')
- const npm = require('./npm.js')
- const output = require('./utils/output.js')
- const parseJSON = require('./utils/parse-json.js')
- const path = require('path')
- const semver = require('semver')
- const stringifyPackage = require('stringify-package')
- const writeFileAtomic = require('write-file-atomic')
- version.usage = 'npm version [<newversion> | major | minor | patch | premajor | preminor | prepatch | prerelease [--preid=<prerelease-id>] | from-git]' +
- '\n(run in package dir)\n' +
- "'npm -v' or 'npm --version' to print npm version " +
- '(' + npm.version + ')\n' +
- "'npm view <pkg> version' to view a package's " +
- 'published version\n' +
- "'npm ls' to inspect current package/dependency versions"
- // npm version <newver>
- module.exports = version
- function version (args, silent, cb_) {
- if (typeof cb_ !== 'function') {
- cb_ = silent
- silent = false
- }
- if (args.length > 1) return cb_(version.usage)
- readPackage(function (er, data, indent, newline) {
- if (!args.length) return dump(data, cb_)
- if (er) {
- log.error('version', 'No valid package.json found')
- return cb_(er)
- }
- if (args[0] === 'from-git') {
- retrieveTagVersion(silent, data, cb_)
- } else {
- var newVersion = semver.valid(args[0])
- if (!newVersion) newVersion = semver.inc(data.version, args[0], npm.config.get('preid'))
- if (!newVersion) return cb_(version.usage)
- persistVersion(newVersion, silent, data, cb_)
- }
- })
- }
- function retrieveTagVersion (silent, data, cb_) {
- chain([
- verifyGit,
- parseLastGitTag
- ], function (er, results) {
- if (er) return cb_(er)
- var localData = {
- hasGit: true,
- existingTag: true
- }
- var version = results[results.length - 1]
- persistVersion(version, silent, data, localData, cb_)
- })
- }
- function parseLastGitTag (cb) {
- var options = { env: process.env }
- git.whichAndExec(['describe', '--abbrev=0'], options, function (er, stdout) {
- if (er) {
- if (er.message.indexOf('No names found') !== -1) return cb(new Error('No tags found'))
- return cb(er)
- }
- var tag = stdout.trim()
- var prefix = npm.config.get('tag-version-prefix')
- // Strip the prefix from the start of the tag:
- if (tag.indexOf(prefix) === 0) tag = tag.slice(prefix.length)
- var version = semver.valid(tag)
- if (!version) return cb(new Error(tag + ' is not a valid version'))
- cb(null, version)
- })
- }
- function persistVersion (newVersion, silent, data, localData, cb_) {
- if (typeof localData === 'function') {
- cb_ = localData
- localData = {}
- }
- if (!npm.config.get('allow-same-version') && data.version === newVersion) {
- return cb_(new Error('Version not changed, might want --allow-same-version'))
- }
- data.version = newVersion
- var lifecycleData = Object.create(data)
- lifecycleData._id = data.name + '@' + newVersion
- var where = npm.prefix
- chain([
- !localData.hasGit && [checkGit, localData],
- [lifecycle, lifecycleData, 'preversion', where],
- [updatePackage, newVersion, silent],
- [lifecycle, lifecycleData, 'version', where],
- [commit, localData, newVersion],
- [lifecycle, lifecycleData, 'postversion', where]
- ], cb_)
- }
- function readPackage (cb) {
- var packagePath = path.join(npm.localPrefix, 'package.json')
- fs.readFile(packagePath, 'utf8', function (er, data) {
- if (er) return cb(new Error(er))
- var indent
- var newline
- try {
- indent = detectIndent(data).indent
- newline = detectNewline(data)
- data = JSON.parse(data)
- } catch (e) {
- er = e
- data = null
- }
- cb(er, data, indent, newline)
- })
- }
- function updatePackage (newVersion, silent, cb_) {
- function cb (er) {
- if (!er && !silent) output('v' + newVersion)
- cb_(er)
- }
- readPackage(function (er, data, indent, newline) {
- if (er) return cb(new Error(er))
- data.version = newVersion
- write(data, 'package.json', indent, newline, cb)
- })
- }
- function commit (localData, newVersion, cb) {
- updateShrinkwrap(newVersion, function (er, hasShrinkwrap, hasLock) {
- if (er || !localData.hasGit) return cb(er)
- localData.hasShrinkwrap = hasShrinkwrap
- localData.hasPackageLock = hasLock
- _commit(newVersion, localData, cb)
- })
- }
- const SHRINKWRAP = 'npm-shrinkwrap.json'
- const PKGLOCK = 'package-lock.json'
- function readLockfile (name) {
- return readFile(
- path.join(npm.localPrefix, name), 'utf8'
- ).catch({code: 'ENOENT'}, () => null)
- }
- function updateShrinkwrap (newVersion, cb) {
- BB.join(
- readLockfile(SHRINKWRAP),
- readLockfile(PKGLOCK),
- (shrinkwrap, lockfile) => {
- if (!shrinkwrap && !lockfile) {
- return cb(null, false, false)
- }
- const file = shrinkwrap ? SHRINKWRAP : PKGLOCK
- let data
- let indent
- let newline
- try {
- data = parseJSON(shrinkwrap || lockfile)
- indent = detectIndent(shrinkwrap || lockfile).indent
- newline = detectNewline(shrinkwrap || lockfile)
- } catch (err) {
- log.error('version', `Bad ${file} data.`)
- return cb(err)
- }
- data.version = newVersion
- write(data, file, indent, newline, (err) => {
- if (err) {
- log.error('version', `Failed to update version in ${file}`)
- return cb(err)
- } else {
- return cb(null, !!shrinkwrap, !!lockfile)
- }
- })
- }
- )
- }
- function dump (data, cb) {
- var v = {}
- if (data && data.name && data.version) v[data.name] = data.version
- v.npm = npm.version
- Object.keys(process.versions).sort().forEach(function (k) {
- v[k] = process.versions[k]
- })
- if (npm.config.get('json')) v = JSON.stringify(v, null, 2)
- output(v)
- cb()
- }
- function statGitFolder (cb) {
- fs.stat(path.join(npm.localPrefix, '.git'), cb)
- }
- function callGitStatus (cb) {
- git.whichAndExec(
- [ 'status', '--porcelain' ],
- { env: process.env },
- cb
- )
- }
- function cleanStatusLines (stdout) {
- var lines = stdout.trim().split('\n').filter(function (line) {
- return line.trim() && !line.match(/^\?\? /)
- }).map(function (line) {
- return line.trim()
- })
- return lines
- }
- function verifyGit (cb) {
- function checkStatus (er) {
- if (er) return cb(er)
- callGitStatus(checkStdout)
- }
- function checkStdout (er, stdout) {
- if (er) return cb(er)
- var lines = cleanStatusLines(stdout)
- if (lines.length > 0) {
- return cb(new Error(
- 'Git working directory not clean.\n' + lines.join('\n')
- ))
- }
- cb()
- }
- statGitFolder(checkStatus)
- }
- function checkGit (localData, cb) {
- statGitFolder(function (er) {
- var doGit = !er && npm.config.get('git-tag-version')
- if (!doGit) {
- if (er && npm.config.get('git-tag-version')) log.verbose('version', 'error checking for .git', er)
- log.verbose('version', 'not tagging in git')
- return cb(null, false)
- }
- // check for git
- callGitStatus(function (er, stdout) {
- if (er && er.code === 'ENOGIT') {
- log.warn(
- 'version',
- 'This is a Git checkout, but the git command was not found.',
- 'npm could not create a Git tag for this release!'
- )
- return cb(null, false)
- }
- var lines = cleanStatusLines(stdout)
- 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)
- })
- })
- }
- module.exports.buildCommitArgs = buildCommitArgs
- function buildCommitArgs (args) {
- const add = []
- args = args || []
- if (args[0] === 'commit') args.shift()
- if (!npm.config.get('commit-hooks')) add.push('-n')
- if (npm.config.get('allow-same-version')) add.push('--allow-empty')
- return ['commit', ...add, ...args]
- }
- module.exports.buildTagFlags = buildTagFlags
- function buildTagFlags () {
- return '-'.concat(
- npm.config.get('sign-git-tag') ? 's' : '',
- npm.config.get('allow-same-version') ? 'f' : '',
- 'm'
- )
- }
- function _commit (version, localData, cb) {
- const options = { env: process.env }
- const message = npm.config.get('message').replace(/%s/g, version)
- const signCommit = npm.config.get('sign-git-commit')
- const commitArgs = buildCommitArgs([
- 'commit',
- ...(signCommit ? ['-S', '-m'] : ['-m']),
- message
- ])
- stagePackageFiles(localData, options).then(() => {
- return git.exec(commitArgs, options)
- }).then(() => {
- if (!localData.existingTag) {
- return git.exec([
- 'tag', npm.config.get('tag-version-prefix') + version,
- buildTagFlags(), message
- ], options)
- }
- }).nodeify(cb)
- }
- function stagePackageFiles (localData, options) {
- return addLocalFile('package.json', options, false).then(() => {
- if (localData.hasShrinkwrap) {
- return addLocalFile('npm-shrinkwrap.json', options, true)
- } else if (localData.hasPackageLock) {
- return addLocalFile('package-lock.json', options, true)
- }
- })
- }
- function addLocalFile (file, options, ignoreFailure) {
- const p = git.exec(['add', path.join(npm.localPrefix, file)], options)
- return ignoreFailure
- ? p.catch(() => {})
- : p
- }
- function write (data, file, indent, newline, cb) {
- assert(data && typeof data === 'object', 'must pass data to version write')
- assert(typeof file === 'string', 'must pass filename to write to version write')
- log.verbose('version.write', 'data', data, 'to', file)
- writeFileAtomic(
- path.join(npm.localPrefix, file),
- stringifyPackage(data, indent, newline),
- cb
- )
- }
|