forked from npm/cli
-
Notifications
You must be signed in to change notification settings - Fork 0
/
version.js
355 lines (312 loc) · 9.79 KB
/
version.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
'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
)
}