7 1. check for a new version of pkg
9 If no packages are specified, then run for all installed
14 module.exports = outdated
16 outdated.usage = "npm outdated [<pkg> [<pkg> ...]]"
18 outdated.completion = require("./utils/completion/installed-deep.js")
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")
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))
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")
48 var tableOpts = { align: ["l", "r", "r", "r", "l"]
49 , stringLength: function(s) { return ansiTrim(s).length }
51 console.log(table(outTable, tableOpts))
57 // [[ dir, dep, has, want ]]
58 function makePretty (p) {
59 var parseable = npm.config.get("parseable")
60 , long = npm.config.get("long")
62 , dir = path.resolve(p[0], "node_modules", dep)
67 // XXX add --json support
68 // Should match (more or less) the output of ls --json
72 if (npm.config.get("long")) {
73 str += ":" + dep + "@" + want
74 + ":" + (has ? (dep + "@" + has) : "MISSING")
79 if (!npm.config.get("global")) {
80 dir = path.relative(process.cwd(), dir)
82 return [ has === want ? color.yellow(dep) : color.red(dep)
85 , color.magenta(latest)
86 , color.brightBlack(dirToPrettyLocation(dir))
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, "")
96 function dirToPrettyLocation (dir) {
97 return dir.replace(/^node_modules[/\\]/, "")
98 .replace(/[[/\\]node_modules[/\\]/g, " > ")
101 function makeJSON (list) {
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)
108 out[p[1]] = { current: p[2]
114 return JSON.stringify(out, null, 2)
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]
126 if (depth > npm.config.get("depth")) {
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]
148 fs.readdir(path.resolve(dir, "node_modules"), function (er, pkgs) {
150 has = Object.create(parentHas)
153 pkgs = pkgs.filter(function (p) {
154 return !p.match(/^[\._-]/)
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]])
162 }, function (er, pvs) {
163 if (er) return cb(er)
164 has = Object.create(parentHas)
165 pvs.forEach(function (pv) {
177 if (!has || !deps) return
179 deps = Object.keys(has).reduce(function (l, r) {
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)
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.
199 // { version: , from: }
204 , path.resolve(dir, "node_modules", dep)
210 function doIt (wanted, latest) {
211 cb(null, [[ dir, dep, curr && curr.version, wanted, latest, req ]])
214 if (args.length && args.indexOf(dep) === -1) {
218 if (isGitUrl(url.parse(req)))
219 return doIt("git", "git")
221 var registry = npm.registry
222 // search for the latest package
223 registry.get(dep + "/latest", function (er, l) {
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()
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
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)