UNPKG

11.6 kBJavaScriptView Raw
1/*
2
3npm outdated [pkg]
4
5Does the following:
6
71. check for a new version of pkg
8
9If no packages are specified, then run for all installed
10packages.
11
12--parseable creates output like this:
13<fullpath>:<name@wanted>:<name@installed>:<name@latest>
14
15*/
16
17module.exports = outdated
18
19outdated.usage = 'npm outdated [[<@scope>/]<pkg> ...]'
20
21outdated.completion = require('./utils/completion/installed-deep.js')
22
23var os = require('os')
24var url = require('url')
25var path = require('path')
26var readPackageTree = require('read-package-tree')
27var asyncMap = require('slide').asyncMap
28var color = require('ansicolors')
29var styles = require('ansistyles')
30var table = require('text-table')
31var semver = require('semver')
32var npa = require('npm-package-arg')
33var pickManifest = require('npm-pick-manifest')
34var fetchPackageMetadata = require('./fetch-package-metadata.js')
35var mutateIntoLogicalTree = require('./install/mutate-into-logical-tree.js')
36var npm = require('./npm.js')
37var long = npm.config.get('long')
38var mapToRegistry = require('./utils/map-to-registry.js')
39var isExtraneous = require('./install/is-extraneous.js')
40var computeMetadata = require('./install/deps.js').computeMetadata
41var computeVersionSpec = require('./install/deps.js').computeVersionSpec
42var moduleName = require('./utils/module-name.js')
43var output = require('./utils/output.js')
44var ansiTrim = require('./utils/ansi-trim')
45
46function uniq (list) {
47 // we maintain the array because we need an array, not iterator, return
48 // value.
49 var uniqed = []
50 var seen = new Set()
51 list.forEach(function (item) {
52 if (seen.has(item)) return
53 seen.add(item)
54 uniqed.push(item)
55 })
56 return uniqed
57}
58
59function andComputeMetadata (next) {
60 return function (er, tree) {
61 if (er) return next(er)
62 next(null, computeMetadata(tree))
63 }
64}
65
66function outdated (args, silent, cb) {
67 if (typeof cb !== 'function') {
68 cb = silent
69 silent = false
70 }
71 var dir = path.resolve(npm.dir, '..')
72
73 // default depth for `outdated` is 0 (cf. `ls`)
74 if (npm.config.get('depth') === Infinity) npm.config.set('depth', 0)
75
76 readPackageTree(dir, andComputeMetadata(function (er, tree) {
77 if (!tree) return cb(er)
78 mutateIntoLogicalTree(tree)
79 outdated_(args, '', tree, {}, 0, function (er, list) {
80 list = uniq(list || []).sort(function (aa, bb) {
81 return aa[0].path.localeCompare(bb[0].path) ||
82 aa[1].localeCompare(bb[1])
83 })
84 if (er || silent || list.length === 0) return cb(er, list)
85 if (npm.config.get('json')) {
86 output(makeJSON(list))
87 } else if (npm.config.get('parseable')) {
88 output(makeParseable(list))
89 } else {
90 var outList = list.map(makePretty)
91 var outHead = [ 'Package',
92 'Current',
93 'Wanted',
94 'Latest',
95 'Location'
96 ]
97 if (long) outHead.push('Package Type')
98 var outTable = [outHead].concat(outList)
99
100 if (npm.color) {
101 outTable[0] = outTable[0].map(function (heading) {
102 return styles.underline(heading)
103 })
104 }
105
106 var tableOpts = {
107 align: ['l', 'r', 'r', 'r', 'l'],
108 stringLength: function (s) { return ansiTrim(s).length }
109 }
110 output(table(outTable, tableOpts))
111 }
112 process.exitCode = 1
113 cb(null, list.map(function (item) { return [item[0].parent.path].concat(item.slice(1, 7)) }))
114 })
115 }))
116}
117
118// [[ dir, dep, has, want, latest, type ]]
119function makePretty (p) {
120 var depname = p[1]
121 var has = p[2]
122 var want = p[3]
123 var latest = p[4]
124 var type = p[6]
125 var deppath = p[7]
126
127 var columns = [ depname,
128 has || 'MISSING',
129 want,
130 latest,
131 deppath
132 ]
133 if (long) columns[5] = type
134
135 if (npm.color) {
136 columns[0] = color[has === want || want === 'linked' ? 'yellow' : 'red'](columns[0]) // dep
137 columns[2] = color.green(columns[2]) // want
138 columns[3] = color.magenta(columns[3]) // latest
139 }
140
141 return columns
142}
143
144function makeParseable (list) {
145 return list.map(function (p) {
146 var dep = p[0]
147 var depname = p[1]
148 var dir = dep.path
149 var has = p[2]
150 var want = p[3]
151 var latest = p[4]
152 var type = p[6]
153
154 var out = [
155 dir,
156 depname + '@' + want,
157 (has ? (depname + '@' + has) : 'MISSING'),
158 depname + '@' + latest
159 ]
160 if (long) out.push(type)
161
162 return out.join(':')
163 }).join(os.EOL)
164}
165
166function makeJSON (list) {
167 var out = {}
168 list.forEach(function (p) {
169 var dep = p[0]
170 var depname = p[1]
171 var dir = dep.path
172 var has = p[2]
173 var want = p[3]
174 var latest = p[4]
175 var type = p[6]
176 if (!npm.config.get('global')) {
177 dir = path.relative(process.cwd(), dir)
178 }
179 out[depname] = { current: has,
180 wanted: want,
181 latest: latest,
182 location: dir
183 }
184 if (long) out[depname].type = type
185 })
186 return JSON.stringify(out, null, 2)
187}
188
189function outdated_ (args, path, tree, parentHas, depth, cb) {
190 if (!tree.package) tree.package = {}
191 if (path && tree.package.name) path += ' > ' + tree.package.name
192 if (!path && tree.package.name) path = tree.package.name
193 if (depth > npm.config.get('depth')) {
194 return cb(null, [])
195 }
196 var types = {}
197 var pkg = tree.package
198
199 if (!tree.children) tree.children = []
200
201 var deps = tree.error ? tree.children : tree.children.filter((child) => !isExtraneous(child))
202
203 deps.forEach(function (dep) {
204 types[moduleName(dep)] = 'dependencies'
205 })
206
207 Object.keys(tree.missingDeps || {}).forEach(function (name) {
208 deps.push({
209 package: { name: name },
210 path: tree.path,
211 parent: tree,
212 isMissing: true
213 })
214 types[name] = 'dependencies'
215 })
216
217 // If we explicitly asked for dev deps OR we didn't ask for production deps
218 // AND we asked to save dev-deps OR we didn't ask to save anything that's NOT
219 // dev deps then…
220 // (All the save checking here is because this gets called from npm-update currently
221 // and that requires this logic around dev deps.)
222 // FIXME: Refactor npm update to not be in terms of outdated.
223 var dev = npm.config.get('dev') || /^dev(elopment)?$/.test(npm.config.get('also'))
224 var prod = npm.config.get('production') || /^prod(uction)?$/.test(npm.config.get('only'))
225 if ((dev || !prod) &&
226 (npm.config.get('save-dev') || (
227 !npm.config.get('save') && !npm.config.get('save-optional')))) {
228 Object.keys(tree.missingDevDeps).forEach(function (name) {
229 deps.push({
230 package: { name: name },
231 path: tree.path,
232 parent: tree,
233 isMissing: true
234 })
235 if (!types[name]) {
236 types[name] = 'devDependencies'
237 }
238 })
239 }
240
241 if (npm.config.get('save-dev')) {
242 deps = deps.filter(function (dep) { return pkg.devDependencies[moduleName(dep)] })
243 deps.forEach(function (dep) {
244 types[moduleName(dep)] = 'devDependencies'
245 })
246 } else if (npm.config.get('save')) {
247 // remove optional dependencies from dependencies during --save.
248 deps = deps.filter(function (dep) { return !pkg.optionalDependencies[moduleName(dep)] })
249 } else if (npm.config.get('save-optional')) {
250 deps = deps.filter(function (dep) { return pkg.optionalDependencies[moduleName(dep)] })
251 deps.forEach(function (dep) {
252 types[moduleName(dep)] = 'optionalDependencies'
253 })
254 }
255 var doUpdate = dev || (
256 !prod &&
257 !Object.keys(parentHas).length &&
258 !npm.config.get('global')
259 )
260 if (doUpdate) {
261 Object.keys(pkg.devDependencies || {}).forEach(function (k) {
262 if (!(k in parentHas)) {
263 deps[k] = pkg.devDependencies[k]
264 types[k] = 'devDependencies'
265 }
266 })
267 }
268
269 var has = Object.create(parentHas)
270 tree.children.forEach(function (child) {
271 if (child.package.name && child.package.private) {
272 deps = deps.filter(function (dep) { return dep !== child })
273 }
274 has[child.package.name] = {
275 version: child.isLink ? 'linked' : child.package.version,
276 from: child.isLink ? 'file:' + child.path : child.package._from
277 }
278 })
279
280 // now get what we should have, based on the dep.
281 // if has[dep] !== shouldHave[dep], then cb with the data
282 // otherwise dive into the folder
283 asyncMap(deps, function (dep, cb) {
284 var name = moduleName(dep)
285 var required
286 if (tree.package.dependencies && name in tree.package.dependencies) {
287 required = tree.package.dependencies[name]
288 } else if (tree.package.optionalDependencies && name in tree.package.optionalDependencies) {
289 required = tree.package.optionalDependencies[name]
290 } else if (tree.package.devDependencies && name in tree.package.devDependencies) {
291 required = tree.package.devDependencies[name]
292 } else if (has[name]) {
293 required = computeVersionSpec(tree, dep)
294 }
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
302function shouldUpdate (args, tree, dep, has, req, depth, pkgpath, cb, type) {
303 // look up the most recent version.
304 // if that's what we already have, or if it's not on the args list,
305 // then dive into it. Otherwise, cb() with the data.
306
307 // { version: , from: }
308 var curr = has[dep]
309
310 function skip (er) {
311 // show user that no viable version can be found
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
330 if (tree.isLink && req == null) return skip()
331
332 if (req == null || req === '') req = '*'
333
334 var parsed = npa.resolve(dep, req)
335 if (parsed.type === 'directory') {
336 if (tree.isLink) {
337 return skip()
338 } else {
339 return doIt('linked', 'linked')
340 }
341 } else if (parsed.type === 'git') {
342 return doIt('git', 'git')
343 } else if (parsed.type === 'file') {
344 return updateLocalDeps()
345 } else {
346 return mapToRegistry(dep, npm.config, function (er, uri, auth) {
347 if (er) return cb(er)
348
349 npm.registry.get(uri, { auth: auth }, updateDeps)
350 })
351 }
352
353 function updateLocalDeps (latestRegistryVersion) {
354 fetchPackageMetadata('file:' + parsed.fetchSpec, '.', (er, localDependency) => {
355 if (er) return cb()
356
357 var wanted = localDependency.version
358 var latest = localDependency.version
359
360 if (latestRegistryVersion) {
361 latest = latestRegistryVersion
362 if (semver.lt(wanted, latestRegistryVersion)) {
363 wanted = latestRegistryVersion
364 req = dep + '@' + latest
365 }
366 }
367
368 if (!curr || curr.version !== wanted) {
369 doIt(wanted, latest)
370 } else {
371 skip()
372 }
373 })
374 }
375
376 function updateDeps (er, d) {
377 if (er) return cb(er)
378
379 try {
380 var l = pickManifest(d, 'latest')
381 var m = pickManifest(d, req)
382 } catch (er) {
383 if (er.code === 'ETARGET') {
384 return skip(er)
385 } else {
386 return skip()
387 }
388 }
389
390 // check that the url origin hasn't changed (#1727) and that
391 // there is no newer version available
392 var dFromUrl = m._from && url.parse(m._from).protocol
393 var cFromUrl = curr && curr.from && url.parse(curr.from).protocol
394
395 if (!curr ||
396 (dFromUrl && cFromUrl && m._from !== curr.from) ||
397 m.version !== curr.version ||
398 m.version !== l.version) {
399 doIt(m.version, l.version)
400 } else {
401 skip()
402 }
403 }
404}