1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 | module.exports = outdated
|
18 |
|
19 | outdated.usage = 'npm outdated [[<@scope>/]<pkg> ...]'
|
20 |
|
21 | outdated.completion = require('./utils/completion/installed-deep.js')
|
22 |
|
23 | var os = require('os')
|
24 | var url = require('url')
|
25 | var path = require('path')
|
26 | var readPackageTree = require('read-package-tree')
|
27 | var readJson = require('read-package-json')
|
28 | var asyncMap = require('slide').asyncMap
|
29 | var color = require('ansicolors')
|
30 | var styles = require('ansistyles')
|
31 | var table = require('text-table')
|
32 | var semver = require('semver')
|
33 | var npa = require('npm-package-arg')
|
34 | var mutateIntoLogicalTree = require('./install/mutate-into-logical-tree.js')
|
35 | var cache = require('./cache.js')
|
36 | var npm = require('./npm.js')
|
37 | var long = npm.config.get('long')
|
38 | var mapToRegistry = require('./utils/map-to-registry.js')
|
39 | var isExtraneous = require('./install/is-extraneous.js')
|
40 | var computeMetadata = require('./install/deps.js').computeMetadata
|
41 | var moduleName = require('./utils/module-name.js')
|
42 | var output = require('./utils/output.js')
|
43 | var ansiTrim = require('./utils/ansi-trim')
|
44 |
|
45 | function uniqName (item) {
|
46 | return item[0].path + '|' + item[1] + '|' + item[7]
|
47 | }
|
48 |
|
49 | function uniq (list) {
|
50 | var uniqed = []
|
51 | var seen = {}
|
52 | list.forEach(function (item) {
|
53 | var name = uniqName(item)
|
54 | if (seen[name]) return
|
55 | seen[name] = true
|
56 | uniqed.push(item)
|
57 | })
|
58 | return uniqed
|
59 | }
|
60 |
|
61 | function andComputeMetadata (next) {
|
62 | return function (er, tree) {
|
63 | if (er) return next(er)
|
64 | next(null, computeMetadata(tree))
|
65 | }
|
66 | }
|
67 |
|
68 | function outdated (args, silent, cb) {
|
69 | if (typeof cb !== 'function') {
|
70 | cb = silent
|
71 | silent = false
|
72 | }
|
73 | var dir = path.resolve(npm.dir, '..')
|
74 |
|
75 |
|
76 | if (npm.config.get('depth') === Infinity) npm.config.set('depth', 0)
|
77 |
|
78 | readPackageTree(dir, andComputeMetadata(function (er, tree) {
|
79 | if (!tree) return cb(er)
|
80 | mutateIntoLogicalTree(tree)
|
81 | outdated_(args, '', tree, {}, 0, function (er, list) {
|
82 | list = uniq(list || []).sort(function (aa, bb) {
|
83 | return aa[0].path.localeCompare(bb[0].path) ||
|
84 | aa[1].localeCompare(bb[1])
|
85 | })
|
86 | if (er || silent || list.length === 0) return cb(er, list)
|
87 | if (npm.config.get('json')) {
|
88 | output(makeJSON(list))
|
89 | } else if (npm.config.get('parseable')) {
|
90 | output(makeParseable(list))
|
91 | } else {
|
92 | var outList = list.map(makePretty)
|
93 | var outHead = [ 'Package',
|
94 | 'Current',
|
95 | 'Wanted',
|
96 | 'Latest',
|
97 | 'Location'
|
98 | ]
|
99 | if (long) outHead.push('Package Type')
|
100 | var outTable = [outHead].concat(outList)
|
101 |
|
102 | if (npm.color) {
|
103 | outTable[0] = outTable[0].map(function (heading) {
|
104 | return styles.underline(heading)
|
105 | })
|
106 | }
|
107 |
|
108 | var tableOpts = {
|
109 | align: ['l', 'r', 'r', 'r', 'l'],
|
110 | stringLength: function (s) { return ansiTrim(s).length }
|
111 | }
|
112 | output(table(outTable, tableOpts))
|
113 | }
|
114 | process.exitCode = 1
|
115 | cb(null, list.map(function (item) { return [item[0].parent.path].concat(item.slice(1, 7)) }))
|
116 | })
|
117 | }))
|
118 | }
|
119 |
|
120 |
|
121 | function makePretty (p) {
|
122 | var dep = p[0]
|
123 | var depname = p[1]
|
124 | var dir = dep.path
|
125 | var has = p[2]
|
126 | var want = p[3]
|
127 | var latest = p[4]
|
128 | var type = p[6]
|
129 | var deppath = p[7]
|
130 |
|
131 | if (!npm.config.get('global')) {
|
132 | dir = path.relative(process.cwd(), dir)
|
133 | }
|
134 |
|
135 | var columns = [ depname,
|
136 | has || 'MISSING',
|
137 | want,
|
138 | latest,
|
139 | deppath
|
140 | ]
|
141 | if (long) columns[5] = type
|
142 |
|
143 | if (npm.color) {
|
144 | columns[0] = color[has === want || want === 'linked' ? 'yellow' : 'red'](columns[0])
|
145 | columns[2] = color.green(columns[2])
|
146 | columns[3] = color.magenta(columns[3])
|
147 | }
|
148 |
|
149 | return columns
|
150 | }
|
151 |
|
152 | function makeParseable (list) {
|
153 | return list.map(function (p) {
|
154 | var dep = p[0]
|
155 | var depname = p[1]
|
156 | var dir = dep.path
|
157 | var has = p[2]
|
158 | var want = p[3]
|
159 | var latest = p[4]
|
160 | var type = p[6]
|
161 |
|
162 | var out = [
|
163 | dir,
|
164 | depname + '@' + want,
|
165 | (has ? (depname + '@' + has) : 'MISSING'),
|
166 | depname + '@' + latest
|
167 | ]
|
168 | if (long) out.push(type)
|
169 |
|
170 | return out.join(':')
|
171 | }).join(os.EOL)
|
172 | }
|
173 |
|
174 | function makeJSON (list) {
|
175 | var out = {}
|
176 | list.forEach(function (p) {
|
177 | var dep = p[0]
|
178 | var depname = p[1]
|
179 | var dir = dep.path
|
180 | var has = p[2]
|
181 | var want = p[3]
|
182 | var latest = p[4]
|
183 | var type = p[6]
|
184 | if (!npm.config.get('global')) {
|
185 | dir = path.relative(process.cwd(), dir)
|
186 | }
|
187 | out[depname] = { current: has,
|
188 | wanted: want,
|
189 | latest: latest,
|
190 | location: dir
|
191 | }
|
192 | if (long) out[depname].type = type
|
193 | })
|
194 | return JSON.stringify(out, null, 2)
|
195 | }
|
196 |
|
197 | function outdated_ (args, path, tree, parentHas, depth, cb) {
|
198 | if (!tree.package) tree.package = {}
|
199 | if (path && tree.package.name) path += ' > ' + tree.package.name
|
200 | if (!path && tree.package.name) path = tree.package.name
|
201 | if (depth > npm.config.get('depth')) {
|
202 | return cb(null, [])
|
203 | }
|
204 | var types = {}
|
205 | var pkg = tree.package
|
206 |
|
207 | var deps = tree.children.filter(function (child) { return !isExtraneous(child) }) || []
|
208 |
|
209 | deps.forEach(function (dep) {
|
210 | types[moduleName(dep)] = 'dependencies'
|
211 | })
|
212 |
|
213 | Object.keys(tree.missingDeps).forEach(function (name) {
|
214 | deps.push({
|
215 | package: { name: name },
|
216 | path: tree.path,
|
217 | parent: tree,
|
218 | isMissing: true
|
219 | })
|
220 | types[name] = 'dependencies'
|
221 | })
|
222 |
|
223 |
|
224 |
|
225 |
|
226 |
|
227 |
|
228 |
|
229 | var dev = npm.config.get('dev') || /^dev(elopment)?$/.test(npm.config.get('also'))
|
230 | var prod = npm.config.get('production') || /^prod(uction)?$/.test(npm.config.get('only'))
|
231 | if ((dev || !prod) &&
|
232 | (npm.config.get('save-dev') || (
|
233 | !npm.config.get('save') && !npm.config.get('save-optional')))) {
|
234 | Object.keys(tree.missingDevDeps).forEach(function (name) {
|
235 | deps.push({
|
236 | package: { name: name },
|
237 | path: tree.path,
|
238 | parent: tree,
|
239 | isMissing: true
|
240 | })
|
241 | if (!types[name]) {
|
242 | types[name] = 'devDependencies'
|
243 | }
|
244 | })
|
245 | }
|
246 |
|
247 | if (npm.config.get('save-dev')) {
|
248 | deps = deps.filter(function (dep) { return pkg.devDependencies[moduleName(dep)] })
|
249 | deps.forEach(function (dep) {
|
250 | types[moduleName(dep)] = 'devDependencies'
|
251 | })
|
252 | } else if (npm.config.get('save')) {
|
253 |
|
254 | deps = deps.filter(function (dep) { return !pkg.optionalDependencies[moduleName(dep)] })
|
255 | } else if (npm.config.get('save-optional')) {
|
256 | deps = deps.filter(function (dep) { return pkg.optionalDependencies[moduleName(dep)] })
|
257 | deps.forEach(function (dep) {
|
258 | types[moduleName(dep)] = 'optionalDependencies'
|
259 | })
|
260 | }
|
261 | var doUpdate = dev || (
|
262 | !prod &&
|
263 | !Object.keys(parentHas).length &&
|
264 | !npm.config.get('global')
|
265 | )
|
266 | if (doUpdate) {
|
267 | Object.keys(pkg.devDependencies).forEach(function (k) {
|
268 | if (!(k in parentHas)) {
|
269 | deps[k] = pkg.devDependencies[k]
|
270 | types[k] = 'devDependencies'
|
271 | }
|
272 | })
|
273 | }
|
274 |
|
275 | var has = Object.create(parentHas)
|
276 | tree.children.forEach(function (child) {
|
277 | if (child.package.name && child.package.private) {
|
278 | deps = deps.filter(function (dep) { return dep !== child })
|
279 | }
|
280 | has[child.package.name] = {
|
281 | version: child.package.version,
|
282 | from: child.package._from
|
283 | }
|
284 | })
|
285 |
|
286 |
|
287 |
|
288 |
|
289 | asyncMap(deps, function (dep, cb) {
|
290 | var name = moduleName(dep)
|
291 | var required = (tree.package.dependencies)[name] ||
|
292 | (tree.package.optionalDependencies)[name] ||
|
293 | (tree.package.devDependencies)[name] ||
|
294 | dep.package._requested && dep.package._requested.fetchSpec ||
|
295 | '*'
|
296 | if (!long) return shouldUpdate(args, dep, name, has, required, depth, path, cb)
|
297 |
|
298 | shouldUpdate(args, dep, name, has, required, depth, path, cb, types[name])
|
299 | }, cb)
|
300 | }
|
301 |
|
302 | function shouldUpdate (args, tree, dep, has, req, depth, pkgpath, cb, type) {
|
303 |
|
304 |
|
305 |
|
306 |
|
307 |
|
308 | var curr = has[dep]
|
309 |
|
310 | function skip (er) {
|
311 |
|
312 | if (er) return cb(er)
|
313 | outdated_(args,
|
314 | pkgpath,
|
315 | tree,
|
316 | has,
|
317 | depth + 1,
|
318 | cb)
|
319 | }
|
320 |
|
321 | function doIt (wanted, latest) {
|
322 | if (!long) {
|
323 | return cb(null, [[tree, dep, curr && curr.version, wanted, latest, req, null, pkgpath]])
|
324 | }
|
325 | cb(null, [[tree, dep, curr && curr.version, wanted, latest, req, type, pkgpath]])
|
326 | }
|
327 |
|
328 | if (args.length && args.indexOf(dep) === -1) return skip()
|
329 | var parsed = npa.resolve(dep, req)
|
330 | if (tree.isLink && tree.parent && tree.parent.isTop) {
|
331 | return doIt('linked', 'linked')
|
332 | }
|
333 | if (parsed.type === 'git' || parsed.type === 'hosted') {
|
334 | return doIt('git', 'git')
|
335 | }
|
336 |
|
337 |
|
338 | mapToRegistry(dep, npm.config, function (er, uri, auth) {
|
339 | if (er) return cb(er)
|
340 |
|
341 | npm.registry.get(uri, { auth: auth }, updateDeps)
|
342 | })
|
343 |
|
344 | function updateLocalDeps (latestRegistryVersion) {
|
345 | readJson(path.resolve(parsed.fetchSpec, 'package.json'), function (er, localDependency) {
|
346 | if (er) return cb()
|
347 |
|
348 | var wanted = localDependency.version
|
349 | var latest = localDependency.version
|
350 |
|
351 | if (latestRegistryVersion) {
|
352 | latest = latestRegistryVersion
|
353 | if (semver.lt(wanted, latestRegistryVersion)) {
|
354 | wanted = latestRegistryVersion
|
355 | req = dep + '@' + latest
|
356 | }
|
357 | }
|
358 |
|
359 | if (!curr || curr.version !== wanted) {
|
360 | doIt(wanted, latest)
|
361 | } else {
|
362 | skip()
|
363 | }
|
364 | })
|
365 | }
|
366 |
|
367 | function updateDeps (er, d) {
|
368 | if (er) {
|
369 | if (parsed.type !== 'directory' && parsed.type !== 'file') return cb(er)
|
370 | return updateLocalDeps()
|
371 | }
|
372 |
|
373 | if (!d || !d['dist-tags'] || !d.versions) return cb()
|
374 | var l = d.versions[d['dist-tags'].latest]
|
375 | if (!l) return cb()
|
376 |
|
377 | var r = req
|
378 | if (d['dist-tags'][req]) {
|
379 | r = d['dist-tags'][req]
|
380 | }
|
381 |
|
382 | if (semver.validRange(r, true)) {
|
383 |
|
384 |
|
385 | var vers = Object.keys(d.versions)
|
386 | var v = semver.maxSatisfying(vers, r, true)
|
387 | if (v) {
|
388 | return onCacheAdd(null, d.versions[v])
|
389 | }
|
390 | }
|
391 |
|
392 |
|
393 | cache.add(dep, req, null, false, onCacheAdd)
|
394 |
|
395 | function onCacheAdd (er, d) {
|
396 |
|
397 |
|
398 | if (er) {
|
399 | if (er.code && er.code === 'ETARGET') {
|
400 |
|
401 | return skip(er)
|
402 | }
|
403 | return skip()
|
404 | }
|
405 |
|
406 |
|
407 |
|
408 | var dFromUrl = d._from && url.parse(d._from).protocol
|
409 | var cFromUrl = curr && curr.from && url.parse(curr.from).protocol
|
410 |
|
411 | if (!curr ||
|
412 | dFromUrl && cFromUrl && d._from !== curr.from ||
|
413 | d.version !== curr.version ||
|
414 | d.version !== l.version) {
|
415 | if (parsed.type === 'file' || parsed.type === 'directory') return updateLocalDeps(l.version)
|
416 |
|
417 | doIt(d.version, l.version)
|
418 | } else {
|
419 | skip()
|
420 | }
|
421 | }
|
422 | }
|
423 | }
|