UNPKG

8.83 kBJavaScriptView Raw
1'use strict'
2const BB = require('bluebird')
3
4const assert = require('assert')
5const chain = require('slide').chain
6const detectIndent = require('detect-indent')
7const fs = BB.promisifyAll(require('graceful-fs'))
8const git = require('./utils/git.js')
9const lifecycle = require('./utils/lifecycle.js')
10const log = require('npmlog')
11const npm = require('./npm.js')
12const output = require('./utils/output.js')
13const parseJSON = require('./utils/parse-json.js')
14const path = require('path')
15const semver = require('semver')
16const writeFileAtomic = require('write-file-atomic')
17
18version.usage = 'npm version [<newversion> | major | minor | patch | premajor | preminor | prepatch | prerelease | from-git]' +
19 '\n(run in package dir)\n' +
20 "'npm -v' or 'npm --version' to print npm version " +
21 '(' + npm.version + ')\n' +
22 "'npm view <pkg> version' to view a package's " +
23 'published version\n' +
24 "'npm ls' to inspect current package/dependency versions"
25
26// npm version <newver>
27module.exports = version
28function version (args, silent, cb_) {
29 if (typeof cb_ !== 'function') {
30 cb_ = silent
31 silent = false
32 }
33 if (args.length > 1) return cb_(version.usage)
34
35 readPackage(function (er, data, indent) {
36 if (!args.length) return dump(data, cb_)
37
38 if (er) {
39 log.error('version', 'No valid package.json found')
40 return cb_(er)
41 }
42
43 if (args[0] === 'from-git') {
44 retrieveTagVersion(silent, data, cb_)
45 } else {
46 var newVersion = semver.valid(args[0])
47 if (!newVersion) newVersion = semver.inc(data.version, args[0])
48 if (!newVersion) return cb_(version.usage)
49 persistVersion(newVersion, silent, data, cb_)
50 }
51 })
52}
53
54function retrieveTagVersion (silent, data, cb_) {
55 chain([
56 verifyGit,
57 parseLastGitTag
58 ], function (er, results) {
59 if (er) return cb_(er)
60 var localData = {
61 hasGit: true,
62 existingTag: true
63 }
64
65 var version = results[results.length - 1]
66 persistVersion(version, silent, data, localData, cb_)
67 })
68}
69
70function parseLastGitTag (cb) {
71 var options = { env: process.env }
72 git.whichAndExec(['describe', '--abbrev=0'], options, function (er, stdout) {
73 if (er) {
74 if (er.message.indexOf('No names found') !== -1) return cb(new Error('No tags found'))
75 return cb(er)
76 }
77
78 var tag = stdout.trim()
79 var prefix = npm.config.get('tag-version-prefix')
80 // Strip the prefix from the start of the tag:
81 if (tag.indexOf(prefix) === 0) tag = tag.slice(prefix.length)
82 var version = semver.valid(tag)
83 if (!version) return cb(new Error(tag + ' is not a valid version'))
84 cb(null, version)
85 })
86}
87
88function persistVersion (newVersion, silent, data, localData, cb_) {
89 if (typeof localData === 'function') {
90 cb_ = localData
91 localData = {}
92 }
93
94 if (!npm.config.get('allow-same-version') && data.version === newVersion) {
95 return cb_(new Error('Version not changed, might want --allow-same-version'))
96 }
97 data.version = newVersion
98 var lifecycleData = Object.create(data)
99 lifecycleData._id = data.name + '@' + newVersion
100
101 var where = npm.prefix
102 chain([
103 !localData.hasGit && [checkGit, localData],
104 [lifecycle, lifecycleData, 'preversion', where],
105 [updatePackage, newVersion, silent],
106 [lifecycle, lifecycleData, 'version', where],
107 [commit, localData, newVersion],
108 [lifecycle, lifecycleData, 'postversion', where]
109 ], cb_)
110}
111
112function readPackage (cb) {
113 var packagePath = path.join(npm.localPrefix, 'package.json')
114 fs.readFile(packagePath, 'utf8', function (er, data) {
115 if (er) return cb(new Error(er))
116 var indent
117 try {
118 indent = detectIndent(data).indent || ' '
119 data = JSON.parse(data)
120 } catch (e) {
121 er = e
122 data = null
123 }
124 cb(er, data, indent)
125 })
126}
127
128function updatePackage (newVersion, silent, cb_) {
129 function cb (er) {
130 if (!er && !silent) output('v' + newVersion)
131 cb_(er)
132 }
133
134 readPackage(function (er, data, indent) {
135 if (er) return cb(new Error(er))
136 data.version = newVersion
137 write(data, 'package.json', indent, cb)
138 })
139}
140
141function commit (localData, newVersion, cb) {
142 updateShrinkwrap(newVersion, function (er, hasShrinkwrap, hasLock) {
143 if (er || !localData.hasGit) return cb(er)
144 localData.hasShrinkwrap = hasShrinkwrap
145 localData.hasPackageLock = hasLock
146 _commit(newVersion, localData, cb)
147 })
148}
149
150const SHRINKWRAP = 'npm-shrinkwrap.json'
151const PKGLOCK = 'package-lock.json'
152
153function readLockfile (name) {
154 return fs.readFileAsync(
155 path.join(npm.localPrefix, name), 'utf8'
156 ).catch({code: 'ENOENT'}, () => null)
157}
158
159function updateShrinkwrap (newVersion, cb) {
160 BB.join(
161 readLockfile(SHRINKWRAP),
162 readLockfile(PKGLOCK),
163 (shrinkwrap, lockfile) => {
164 if (!shrinkwrap && !lockfile) {
165 return cb(null, false, false)
166 }
167 const file = shrinkwrap ? SHRINKWRAP : PKGLOCK
168 let data
169 let indent
170 try {
171 data = parseJSON(shrinkwrap || lockfile)
172 indent = detectIndent(shrinkwrap || lockfile).indent || ' '
173 } catch (err) {
174 log.error('version', `Bad ${file} data.`)
175 return cb(err)
176 }
177 data.version = newVersion
178 write(data, file, indent, (err) => {
179 if (err) {
180 log.error('version', `Failed to update version in ${file}`)
181 return cb(err)
182 } else {
183 return cb(null, !!shrinkwrap, !!lockfile)
184 }
185 })
186 }
187 )
188}
189
190function dump (data, cb) {
191 var v = {}
192
193 if (data && data.name && data.version) v[data.name] = data.version
194 v.npm = npm.version
195 Object.keys(process.versions).sort().forEach(function (k) {
196 v[k] = process.versions[k]
197 })
198
199 if (npm.config.get('json')) v = JSON.stringify(v, null, 2)
200
201 output(v)
202 cb()
203}
204
205function statGitFolder (cb) {
206 fs.stat(path.join(npm.localPrefix, '.git'), cb)
207}
208
209function callGitStatus (cb) {
210 git.whichAndExec(
211 [ 'status', '--porcelain' ],
212 { env: process.env },
213 cb
214 )
215}
216
217function cleanStatusLines (stdout) {
218 var lines = stdout.trim().split('\n').filter(function (line) {
219 return line.trim() && !line.match(/^\?\? /)
220 }).map(function (line) {
221 return line.trim()
222 })
223
224 return lines
225}
226
227function verifyGit (cb) {
228 function checkStatus (er) {
229 if (er) return cb(er)
230 callGitStatus(checkStdout)
231 }
232
233 function checkStdout (er, stdout) {
234 if (er) return cb(er)
235 var lines = cleanStatusLines(stdout)
236 if (lines.length > 0) {
237 return cb(new Error(
238 'Git working directory not clean.\n' + lines.join('\n')
239 ))
240 }
241
242 cb()
243 }
244
245 statGitFolder(checkStatus)
246}
247
248function checkGit (localData, cb) {
249 statGitFolder(function (er) {
250 var doGit = !er && npm.config.get('git-tag-version')
251 if (!doGit) {
252 if (er) log.verbose('version', 'error checking for .git', er)
253 log.verbose('version', 'not tagging in git')
254 return cb(null, false)
255 }
256
257 // check for git
258 callGitStatus(function (er, stdout) {
259 if (er && er.code === 'ENOGIT') {
260 log.warn(
261 'version',
262 'This is a Git checkout, but the git command was not found.',
263 'npm could not create a Git tag for this release!'
264 )
265 return cb(null, false)
266 }
267
268 var lines = cleanStatusLines(stdout)
269 if (lines.length && !npm.config.get('force')) {
270 return cb(new Error(
271 'Git working directory not clean.\n' + lines.join('\n')
272 ))
273 }
274 localData.hasGit = true
275 cb(null, true)
276 })
277 })
278}
279
280function _commit (version, localData, cb) {
281 var packagePath = path.join(npm.localPrefix, 'package.json')
282 var options = { env: process.env }
283 var message = npm.config.get('message').replace(/%s/g, version)
284 var sign = npm.config.get('sign-git-tag')
285 var flag = sign ? '-sm' : '-am'
286 chain(
287 [
288 git.chainableExec([ 'add', packagePath ], options),
289 localData.hasShrinkwrap && git.chainableExec([ 'add', path.join(npm.localPrefix, 'npm-shrinkwrap.json') ], options),
290 localData.hasPackageLock && git.chainableExec([ 'add', path.join(npm.localPrefix, 'package-lock.json') ], options),
291 git.chainableExec([ 'commit', '-m', message ], options),
292 !localData.existingTag && git.chainableExec([
293 'tag',
294 npm.config.get('tag-version-prefix') + version,
295 flag,
296 message
297 ], options)
298 ],
299 cb
300 )
301}
302
303function write (data, file, indent, cb) {
304 assert(data && typeof data === 'object', 'must pass data to version write')
305 assert(typeof file === 'string', 'must pass filename to write to version write')
306
307 log.verbose('version.write', 'data', data, 'to', file)
308 writeFileAtomic(
309 path.join(npm.localPrefix, file),
310 new Buffer(JSON.stringify(data, null, indent || 2) + '\n'),
311 cb
312 )
313}