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