7 1. check for a new version of pkg
9 If no packages are specified, then run for all installed
12 --parseable creates output like this:
13 <fullpath>:<name@wanted>:<name@installed>:<name@latest>
17 module.exports = outdated
19 outdated.usage = "npm outdated [<pkg> [<pkg> ...]]"
21 outdated.completion = require("./utils/completion/installed-deep.js")
24 var path = require("path")
25 , readJson = require("read-package-json")
26 , cache = require("./cache.js")
27 , asyncMap = require("slide").asyncMap
28 , npm = require("./npm.js")
29 , url = require("url")
30 , color = require("ansicolors")
31 , styles = require("ansistyles")
32 , table = require("text-table")
33 , semver = require("semver")
35 , mapToRegistry = require("./utils/map-to-registry.js")
36 , npa = require("npm-package-arg")
37 , readInstalled = require("read-installed")
38 , long = npm.config.get("long")
39 , log = require("npmlog")
41 function outdated (args, silent, cb) {
42 if (typeof cb !== "function") cb = silent, silent = false
43 var dir = path.resolve(npm.dir, "..")
45 // default depth for `outdated` is 0 (cf. `ls`)
46 if (npm.config.get("depth") === Infinity) npm.config.set("depth", 0)
48 outdated_(args, dir, {}, 0, function (er, list) {
50 if (er || silent || list.length === 0) return cb(er, list)
51 list.sort(function(a, b) {
52 var aa = a[1].toLowerCase()
53 , bb = b[1].toLowerCase()
57 if (npm.config.get("json")) {
58 console.log(makeJSON(list))
59 } else if (npm.config.get("parseable")) {
60 console.log(makeParseable(list))
62 var outList = list.map(makePretty)
63 var outHead = [ "Package"
69 if (long) outHead.push("Package Type")
70 var outTable = [outHead].concat(outList)
73 outTable[0] = outTable[0].map(function(heading) {
74 return styles.underline(heading)
78 var tableOpts = { align: ["l", "r", "r", "r", "l"]
79 , stringLength: function(s) { return ansiTrim(s).length }
81 console.log(table(outTable, tableOpts))
87 // [[ dir, dep, has, want, latest, type ]]
88 function makePretty (p) {
90 , dir = path.resolve(p[0], "node_modules", dep)
96 if (!npm.config.get("global")) {
97 dir = path.relative(process.cwd(), dir)
104 , dirToPrettyLocation(dir)
106 if (long) columns[5] = type
109 columns[0] = color[has === want ? "yellow" : "red"](columns[0]) // dep
110 columns[2] = color.green(columns[2]) // want
111 columns[3] = color.magenta(columns[3]) // latest
112 columns[4] = color.brightBlack(columns[4]) // dir
113 if (long) columns[5] = color.brightBlack(columns[5]) // type
119 function ansiTrim (str) {
120 var r = new RegExp("\x1b(?:\\[(?:\\d+[ABCDEFGJKSTm]|\\d+;\\d+[Hfm]|" +
121 "\\d+;\\d+;\\d+m|6n|s|u|\\?25[lh])|\\w)", "g")
122 return str.replace(r, "")
125 function dirToPrettyLocation (dir) {
126 return dir.replace(/^node_modules[/\\]/, "")
127 .replace(/[[/\\]node_modules[/\\]/g, " > ")
130 function makeParseable (list) {
131 return list.map(function (p) {
134 , dir = path.resolve(p[0], "node_modules", dep)
142 , (has ? (dep + "@" + has) : "MISSING")
145 if (long) out.push(type)
151 function makeJSON (list) {
153 list.forEach(function (p) {
154 var dir = path.resolve(p[0], "node_modules", p[1])
155 if (!npm.config.get("global")) {
156 dir = path.relative(process.cwd(), dir)
158 out[p[1]] = { current: p[2]
163 if (long) out[p[1]].type = p[6]
165 return JSON.stringify(out, null, 2)
168 function outdated_ (args, dir, parentHas, depth, cb) {
169 // get the deps from package.json, or {<dir/node_modules/*>:"*"}
170 // asyncMap over deps:
171 // shouldHave = cache.add(dep, req).version
172 // if has === shouldHave then
173 // return outdated(args, dir/node_modules/dep, parentHas + has)
174 // else if dep in args or args is empty
175 // return [dir, dep, has, shouldHave]
177 if (depth > npm.config.get("depth")) {
182 readJson(path.resolve(dir, "package.json"), function (er, d) {
184 if (er && er.code !== "ENOENT" && er.code !== "ENOTDIR") return cb(er)
185 deps = (er) ? true : (d.dependencies || {})
187 Object.keys(deps).forEach(function (k) {
188 types[k] = "dependencies"
192 if (npm.config.get("save-dev")) {
193 deps = d.devDependencies || {}
194 Object.keys(deps).forEach(function (k) {
195 types[k] = "devDependencies"
201 if (npm.config.get("save")) {
202 // remove optional dependencies from dependencies during --save.
203 Object.keys(d.optionalDependencies || {}).forEach(function (k) {
209 if (npm.config.get("save-optional")) {
210 deps = d.optionalDependencies || {}
211 Object.keys(deps).forEach(function (k) {
212 types[k] = "optionalDependencies"
217 var doUpdate = npm.config.get("dev") ||
218 (!npm.config.get("production") &&
219 !Object.keys(parentHas).length &&
220 !npm.config.get("global"))
222 if (!er && d && doUpdate) {
223 Object.keys(d.devDependencies || {}).forEach(function (k) {
224 if (!(k in parentHas)) {
225 deps[k] = d.devDependencies[k]
226 types[k] = "devDependencies"
234 readInstalled(path.resolve(dir), { dev : true }, function (er, data) {
236 has = Object.create(parentHas)
239 var pkgs = Object.keys(data.dependencies)
240 pkgs = pkgs.filter(function (p) {
241 return !p.match(/^[\._-]/)
243 asyncMap(pkgs, function (pkg, cb) {
244 var jsonFile = path.resolve(dir, "node_modules", pkg, "package.json")
245 readJson(jsonFile, function (er, d) {
246 if (er && er.code !== "ENOENT" && er.code !== "ENOTDIR") return cb(er)
247 if (d && d.name && d.private) delete deps[d.name]
248 cb(null, er ? [] : [[d.name, d.version, d._from]])
250 }, function (er, pvs) {
251 if (er) return cb(er)
252 has = Object.create(parentHas)
253 pvs.forEach(function (pv) {
255 link: data.dependencies[pv[0]].link,
266 if (!has || !deps) return
268 deps = Object.keys(has).reduce(function (l, r) {
274 // now get what we should have, based on the dep.
275 // if has[dep] !== shouldHave[dep], then cb with the data
276 // otherwise dive into the folder
277 asyncMap(Object.keys(deps), function (dep, cb) {
278 if (!long) return shouldUpdate(args, dir, dep, has, deps[dep], depth, cb)
280 shouldUpdate(args, dir, dep, has, deps[dep], depth, cb, types[dep])
285 function shouldUpdate (args, dir, dep, has, req, depth, cb, type) {
286 // look up the most recent version.
287 // if that's what we already have, or if it's not on the args list,
288 // then dive into it. Otherwise, cb() with the data.
290 // { version: , from: }
294 // show user that no viable version can be found
295 if (er) return cb(er)
297 , path.resolve(dir, "node_modules", dep)
303 function doIt (wanted, latest) {
305 return cb(null, [[ dir, dep, curr && curr.version, wanted, latest, req]])
307 cb(null, [[ dir, dep, curr && curr.version, wanted, latest, req, type]])
310 if (args.length && args.indexOf(dep) === -1) return skip()
311 var parsed = npa(dep + '@' + req)
312 if (parsed.type === "git" || (parsed.hosted && parsed.hosted.type === "github")) {
313 return doIt("git", "git")
315 if (curr && curr.link) {
316 return doIt("linked", "linked")
319 // search for the latest package
320 mapToRegistry(dep, npm.config, function (er, uri, auth) {
321 if (er) return cb(er)
323 npm.registry.get(uri, { auth : auth }, updateDeps)
326 function updateLocalDeps (latestRegistryVersion) {
327 readJson(path.resolve(parsed.spec, 'package.json'), function (er, localDependency) {
330 var wanted = localDependency.version
331 var latest = localDependency.version
333 if (latestRegistryVersion) {
334 latest = latestRegistryVersion
335 if (semver.lt(wanted, latestRegistryVersion)) {
336 wanted = latestRegistryVersion
337 req = dep + '@' + latest
341 if (curr.version !== wanted) {
349 function updateDeps (er, d) {
351 if (parsed.type !== 'local') return cb(er)
352 return updateLocalDeps()
355 if (!d || !d["dist-tags"] || !d.versions) return cb()
356 var l = d.versions[d["dist-tags"].latest]
360 if (d["dist-tags"][req])
361 r = d["dist-tags"][req]
363 if (semver.validRange(r, true)) {
364 // some kind of semver range.
365 // see if it's in the doc.
366 var vers = Object.keys(d.versions)
367 var v = semver.maxSatisfying(vers, r, true)
369 return onCacheAdd(null, d.versions[v])
373 // We didn't find the version in the doc. See if cache can find it.
374 cache.add(dep, req, null, false, onCacheAdd)
376 function onCacheAdd(er, d) {
377 // if this fails, then it means we can't update this thing.
378 // it's probably a thing that isn't published.
380 if (er.code && er.code === "ETARGET") {
381 // no viable version found
387 // check that the url origin hasn't changed (#1727) and that
388 // there is no newer version available
389 var dFromUrl = d._from && url.parse(d._from).protocol
390 var cFromUrl = curr && curr.from && url.parse(curr.from).protocol
392 if (!curr || dFromUrl && cFromUrl && d._from !== curr.from
393 || d.version !== curr.version
394 || d.version !== l.version) {
395 if (parsed.type === 'local') return updateLocalDeps(l.version)
397 doIt(d.version, l.version)