npm: Upgrade to 1.3.17
[platform/upstream/nodejs.git] / deps / npm / lib / outdated.js
1 /*
2
3 npm outdated [pkg]
4
5 Does the following:
6
7 1. check for a new version of pkg
8
9 If no packages are specified, then run for all installed
10 packages.
11
12 */
13
14 module.exports = outdated
15
16 outdated.usage = "npm outdated [<pkg> [<pkg> ...]]"
17
18 outdated.completion = require("./utils/completion/installed-deep.js")
19
20
21 var path = require("path")
22   , fs = require("graceful-fs")
23   , readJson = require("read-package-json")
24   , cache = require("./cache.js")
25   , asyncMap = require("slide").asyncMap
26   , npm = require("./npm.js")
27   , url = require("url")
28   , isGitUrl = require("./utils/is-git-url.js")
29   , color = require("ansicolors")
30   , styles = require("ansistyles")
31   , table = require("text-table")
32
33 function outdated (args, silent, cb) {
34   if (typeof cb !== "function") cb = silent, silent = false
35   var dir = path.resolve(npm.dir, "..")
36   outdated_(args, dir, {}, 0, function (er, list) {
37     if (er || silent) return cb(er, list)
38     if (npm.config.get("json")) {
39       console.log(makeJSON(list))
40     } else {
41       var outList = list.map(makePretty)
42       var outTable = [[ styles.underline("Package")
43                       , styles.underline("Current")
44                       , styles.underline("Wanted")
45                       , styles.underline("Latest")
46                       , styles.underline("Location")
47                      ]].concat(outList)
48       var tableOpts = { align: ["l", "r", "r", "r", "l"]
49                       , stringLength: function(s) { return ansiTrim(s).length }
50                       }
51       console.log(table(outTable, tableOpts))
52     }
53     cb(null, list)
54   })
55 }
56
57 // [[ dir, dep, has, want ]]
58 function makePretty (p) {
59   var parseable = npm.config.get("parseable")
60     , long = npm.config.get("long")
61     , dep = p[1]
62     , dir = path.resolve(p[0], "node_modules", dep)
63     , has = p[2]
64     , want = p[3]
65     , latest = p[4]
66
67   // XXX add --json support
68   // Should match (more or less) the output of ls --json
69
70   if (parseable) {
71     var str = dir
72     if (npm.config.get("long")) {
73       str += ":" + dep + "@" + want
74            + ":" + (has ? (dep + "@" + has) : "MISSING")
75     }
76     return str
77   }
78
79   if (!npm.config.get("global")) {
80     dir = path.relative(process.cwd(), dir)
81   }
82   return [ has === want ? color.yellow(dep) : color.red(dep)
83          , (has || "MISSING")
84          , color.green(want)
85          , color.magenta(latest)
86          , color.brightBlack(dirToPrettyLocation(dir))
87          ]
88 }
89
90 function ansiTrim (str) {
91   var r = new RegExp("\x1b(?:\\[(?:\\d+[ABCDEFGJKSTm]|\\d+;\\d+[Hfm]|" +
92         "\\d+;\\d+;\\d+m|6n|s|u|\\?25[lh])|\\w)", "g");
93   return str.replace(r, "")
94 }
95
96 function dirToPrettyLocation (dir) {
97   return dir.replace(/^node_modules[/\\]/, "")
98             .replace(/[[/\\]node_modules[/\\]/g, " > ")
99 }
100
101 function makeJSON (list) {
102   var out = {}
103   list.forEach(function (p) {
104     var dir = path.resolve(p[0], "node_modules", p[1])
105     if (!npm.config.get("global")) {
106       dir = path.relative(process.cwd(), dir)
107     }
108     out[p[1]] = { current: p[2]
109                 , wanted: p[3]
110                 , latest: p[4]
111                 , location: dir
112                 }
113   })
114   return JSON.stringify(out, null, 2)
115 }
116
117 function outdated_ (args, dir, parentHas, depth, cb) {
118   // get the deps from package.json, or {<dir/node_modules/*>:"*"}
119   // asyncMap over deps:
120   //   shouldHave = cache.add(dep, req).version
121   //   if has === shouldHave then
122   //     return outdated(args, dir/node_modules/dep, parentHas + has)
123   //   else if dep in args or args is empty
124   //     return [dir, dep, has, shouldHave]
125
126   if (depth > npm.config.get("depth")) {
127     return cb(null, [])
128   }
129   var deps = null
130   readJson(path.resolve(dir, "package.json"), function (er, d) {
131     if (er && er.code !== "ENOENT" && er.code !== "ENOTDIR") return cb(er)
132     deps = (er) ? true : (d.dependencies || {})
133     var doUpdate = npm.config.get("dev") ||
134                     (!npm.config.get("production") &&
135                     !Object.keys(parentHas).length &&
136                     !npm.config.get("global"))
137     if (!er && d && doUpdate) {
138       Object.keys(d.devDependencies || {}).forEach(function (k) {
139         if (!(k in parentHas)) {
140           deps[k] = d.devDependencies[k]
141         }
142       })
143     }
144     return next()
145   })
146
147   var has = null
148   fs.readdir(path.resolve(dir, "node_modules"), function (er, pkgs) {
149     if (er) {
150       has = Object.create(parentHas)
151       return next()
152     }
153     pkgs = pkgs.filter(function (p) {
154       return !p.match(/^[\._-]/)
155     })
156     asyncMap(pkgs, function (pkg, cb) {
157       var jsonFile = path.resolve(dir, "node_modules", pkg, "package.json")
158       readJson(jsonFile, function (er, d) {
159         if (er && er.code !== "ENOENT" && er.code !== "ENOTDIR") return cb(er)
160         cb(null, er ? [] : [[d.name, d.version, d._from]])
161       })
162     }, function (er, pvs) {
163       if (er) return cb(er)
164       has = Object.create(parentHas)
165       pvs.forEach(function (pv) {
166         has[pv[0]] = {
167           version: pv[1],
168           from: pv[2]
169         }
170       })
171
172       next()
173     })
174   })
175
176   function next () {
177     if (!has || !deps) return
178     if (deps === true) {
179       deps = Object.keys(has).reduce(function (l, r) {
180         l[r] = "*"
181         return l
182       }, {})
183     }
184
185     // now get what we should have, based on the dep.
186     // if has[dep] !== shouldHave[dep], then cb with the data
187     // otherwise dive into the folder
188     asyncMap(Object.keys(deps), function (dep, cb) {
189       shouldUpdate(args, dir, dep, has, deps[dep], depth, cb)
190     }, cb)
191   }
192 }
193
194 function shouldUpdate (args, dir, dep, has, req, depth, cb) {
195   // look up the most recent version.
196   // if that's what we already have, or if it's not on the args list,
197   // then dive into it.  Otherwise, cb() with the data.
198
199   // { version: , from: }
200   var curr = has[dep]
201
202   function skip () {
203     outdated_( args
204              , path.resolve(dir, "node_modules", dep)
205              , has
206              , depth + 1
207              , cb )
208   }
209
210   function doIt (wanted, latest) {
211     cb(null, [[ dir, dep, curr && curr.version, wanted, latest, req ]])
212   }
213
214   if (args.length && args.indexOf(dep) === -1) {
215     return skip()
216   }
217
218   if (isGitUrl(url.parse(req)))
219     return doIt("git", "git")
220
221   var registry = npm.registry
222   // search for the latest package
223   registry.get(dep + "/latest", function (er, l) {
224     if (er) return cb()
225     // so, we can conceivably update this.  find out if we need to.
226     cache.add(dep, req, function (er, d) {
227       // if this fails, then it means we can't update this thing.
228       // it's probably a thing that isn't published.
229       if (er) return skip()
230
231       // check that the url origin hasn't changed (#1727) and that
232       // there is no newer version available
233       var dFromUrl = d._from && url.parse(d._from).protocol
234       var cFromUrl = curr && curr.from && url.parse(curr.from).protocol
235
236       if (!curr || dFromUrl && cFromUrl && d._from !== curr.from
237           || d.version !== curr.version
238           || d.version !== l.version)
239         doIt(d.version, l.version)
240       else
241         skip()
242     })
243   })
244 }