UNPKG

8.42 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 [<pkg> [<pkg> ...]]"
20
21outdated.completion = require("./utils/completion/installed-deep.js")
22
23
24var path = require("path")
25 , fs = require("graceful-fs")
26 , readJson = require("read-package-json")
27 , cache = require("./cache.js")
28 , asyncMap = require("slide").asyncMap
29 , npm = require("./npm.js")
30 , url = require("url")
31 , isGitUrl = require("./utils/is-git-url.js")
32 , color = require("ansicolors")
33 , styles = require("ansistyles")
34 , table = require("text-table")
35 , semver = require("semver")
36 , os = require("os")
37
38function outdated (args, silent, cb) {
39 if (typeof cb !== "function") cb = silent, silent = false
40 var dir = path.resolve(npm.dir, "..")
41 outdated_(args, dir, {}, 0, function (er, list) {
42 if (er || silent) return cb(er, list)
43 if (npm.config.get("json")) {
44 console.log(makeJSON(list))
45 } else if (npm.config.get("parseable")) {
46 console.log(makeParseable(list));
47 } else {
48 var outList = list.map(makePretty)
49 var outTable = [[ "Package"
50 , "Current"
51 , "Wanted"
52 , "Latest"
53 , "Location"
54 ]].concat(outList)
55
56 if (npm.color) {
57 outTable[0] = outTable[0].map(function(heading) {
58 return styles.underline(heading)
59 })
60 }
61
62 var tableOpts = { align: ["l", "r", "r", "r", "l"]
63 , stringLength: function(s) { return ansiTrim(s).length }
64 }
65 console.log(table(outTable, tableOpts))
66 }
67 cb(null, list)
68 })
69}
70
71// [[ dir, dep, has, want, latest ]]
72function makePretty (p) {
73 var parseable = npm.config.get("parseable")
74 , dep = p[1]
75 , dir = path.resolve(p[0], "node_modules", dep)
76 , has = p[2]
77 , want = p[3]
78 , latest = p[4]
79
80 if (!npm.config.get("global")) {
81 dir = path.relative(process.cwd(), dir)
82 }
83
84 var columns = [ dep
85 , has || "MISSING"
86 , want
87 , latest
88 , dirToPrettyLocation(dir)
89 ]
90
91 if (npm.color) {
92 columns[0] = color[has === want ? "yellow" : "red"](columns[0]) // dep
93 columns[2] = color.green(columns[2]) // want
94 columns[3] = color.magenta(columns[3]) // latest
95 columns[4] = color.brightBlack(columns[4]) // dir
96 }
97
98 return columns
99}
100
101function ansiTrim (str) {
102 var r = new RegExp("\x1b(?:\\[(?:\\d+[ABCDEFGJKSTm]|\\d+;\\d+[Hfm]|" +
103 "\\d+;\\d+;\\d+m|6n|s|u|\\?25[lh])|\\w)", "g");
104 return str.replace(r, "")
105}
106
107function dirToPrettyLocation (dir) {
108 return dir.replace(/^node_modules[/\\]/, "")
109 .replace(/[[/\\]node_modules[/\\]/g, " > ")
110}
111
112function makeParseable (list) {
113 return list.map(function (p) {
114 var dep = p[1]
115 , dir = path.resolve(p[0], "node_modules", dep)
116 , has = p[2]
117 , want = p[3]
118 , latest = p[4];
119
120 return [ dir
121 , dep + "@" + want
122 , (has ? (dep + "@" + has) : "MISSING")
123 , dep + "@" + latest
124 ].join(":")
125 }).join(os.EOL)
126}
127
128function makeJSON (list) {
129 var out = {}
130 list.forEach(function (p) {
131 var dir = path.resolve(p[0], "node_modules", p[1])
132 if (!npm.config.get("global")) {
133 dir = path.relative(process.cwd(), dir)
134 }
135 out[p[1]] = { current: p[2]
136 , wanted: p[3]
137 , latest: p[4]
138 , location: dir
139 }
140 })
141 return JSON.stringify(out, null, 2)
142}
143
144function outdated_ (args, dir, parentHas, depth, cb) {
145 // get the deps from package.json, or {<dir/node_modules/*>:"*"}
146 // asyncMap over deps:
147 // shouldHave = cache.add(dep, req).version
148 // if has === shouldHave then
149 // return outdated(args, dir/node_modules/dep, parentHas + has)
150 // else if dep in args or args is empty
151 // return [dir, dep, has, shouldHave]
152
153 if (depth > npm.config.get("depth")) {
154 return cb(null, [])
155 }
156 var deps = null
157 readJson(path.resolve(dir, "package.json"), function (er, d) {
158 if (er && er.code !== "ENOENT" && er.code !== "ENOTDIR") return cb(er)
159 deps = (er) ? true : (d.dependencies || {})
160 var doUpdate = npm.config.get("dev") ||
161 (!npm.config.get("production") &&
162 !Object.keys(parentHas).length &&
163 !npm.config.get("global"))
164 if (!er && d && doUpdate) {
165 Object.keys(d.devDependencies || {}).forEach(function (k) {
166 if (!(k in parentHas)) {
167 deps[k] = d.devDependencies[k]
168 }
169 })
170 }
171 return next()
172 })
173
174 var has = null
175 fs.readdir(path.resolve(dir, "node_modules"), function (er, pkgs) {
176 if (er) {
177 has = Object.create(parentHas)
178 return next()
179 }
180 pkgs = pkgs.filter(function (p) {
181 return !p.match(/^[\._-]/)
182 })
183 asyncMap(pkgs, function (pkg, cb) {
184 var jsonFile = path.resolve(dir, "node_modules", pkg, "package.json")
185 readJson(jsonFile, function (er, d) {
186 if (er && er.code !== "ENOENT" && er.code !== "ENOTDIR") return cb(er)
187 cb(null, er ? [] : [[d.name, d.version, d._from]])
188 })
189 }, function (er, pvs) {
190 if (er) return cb(er)
191 has = Object.create(parentHas)
192 pvs.forEach(function (pv) {
193 has[pv[0]] = {
194 version: pv[1],
195 from: pv[2]
196 }
197 })
198
199 next()
200 })
201 })
202
203 function next () {
204 if (!has || !deps) return
205 if (deps === true) {
206 deps = Object.keys(has).reduce(function (l, r) {
207 l[r] = "*"
208 return l
209 }, {})
210 }
211
212 // now get what we should have, based on the dep.
213 // if has[dep] !== shouldHave[dep], then cb with the data
214 // otherwise dive into the folder
215 asyncMap(Object.keys(deps), function (dep, cb) {
216 shouldUpdate(args, dir, dep, has, deps[dep], depth, cb)
217 }, cb)
218 }
219}
220
221function shouldUpdate (args, dir, dep, has, req, depth, cb) {
222 // look up the most recent version.
223 // if that's what we already have, or if it's not on the args list,
224 // then dive into it. Otherwise, cb() with the data.
225
226 // { version: , from: }
227 var curr = has[dep]
228
229 function skip (er) {
230 // show user that no viable version can be found
231 if (er) return cb(er)
232 outdated_( args
233 , path.resolve(dir, "node_modules", dep)
234 , has
235 , depth + 1
236 , cb )
237 }
238
239 function doIt (wanted, latest) {
240 cb(null, [[ dir, dep, curr && curr.version, wanted, latest, req ]])
241 }
242
243 if (args.length && args.indexOf(dep) === -1) {
244 return skip()
245 }
246
247 if (isGitUrl(url.parse(req)))
248 return doIt("git", "git")
249
250 var registry = npm.registry
251 // search for the latest package
252 registry.get(dep, function (er, d) {
253 if (er) return cb()
254 if (!d || !d['dist-tags'] || !d.versions) return cb()
255 var l = d.versions[d['dist-tags'].latest]
256 if (!l) return cb()
257
258 // set to true if found in doc
259 var found = false
260
261 var r = req
262 if (d['dist-tags'][req])
263 r = d['dist-tags'][req]
264
265 if (semver.validRange(r, true)) {
266 // some kind of semver range.
267 // see if it's in the doc.
268 var vers = Object.keys(d.versions)
269 var v = semver.maxSatisfying(vers, r, true)
270 if (v) {
271 return onCacheAdd(null, d.versions[v])
272 }
273 }
274
275 // We didn't find the version in the doc. See if cache can find it.
276 cache.add(dep, req, onCacheAdd)
277
278 function onCacheAdd(er, d) {
279 // if this fails, then it means we can't update this thing.
280 // it's probably a thing that isn't published.
281 if (er) {
282 if (er.code && er.code === 'ETARGET') {
283 // no viable version found
284 return skip(er)
285 }
286 return skip()
287 }
288
289 // check that the url origin hasn't changed (#1727) and that
290 // there is no newer version available
291 var dFromUrl = d._from && url.parse(d._from).protocol
292 var cFromUrl = curr && curr.from && url.parse(curr.from).protocol
293
294 if (!curr || dFromUrl && cFromUrl && d._from !== curr.from
295 || d.version !== curr.version
296 || d.version !== l.version)
297 doIt(d.version, l.version)
298 else
299 skip()
300 }
301
302 })
303}