1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 | module.exports = outdated
|
18 |
|
19 | outdated.usage = "npm outdated [<pkg> [<pkg> ...]]"
|
20 |
|
21 | outdated.completion = require("./utils/completion/installed-deep.js")
|
22 |
|
23 |
|
24 | var 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 |
|
38 | function 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 |
|
72 | function 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])
|
93 | columns[2] = color.green(columns[2])
|
94 | columns[3] = color.magenta(columns[3])
|
95 | columns[4] = color.brightBlack(columns[4])
|
96 | }
|
97 |
|
98 | return columns
|
99 | }
|
100 |
|
101 | function 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 |
|
107 | function dirToPrettyLocation (dir) {
|
108 | return dir.replace(/^node_modules[/\\]/, "")
|
109 | .replace(/[[/\\]node_modules[/\\]/g, " > ")
|
110 | }
|
111 |
|
112 | function 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 |
|
128 | function 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 |
|
144 | function outdated_ (args, dir, parentHas, depth, cb) {
|
145 |
|
146 |
|
147 |
|
148 |
|
149 |
|
150 |
|
151 |
|
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 |
|
213 |
|
214 |
|
215 | asyncMap(Object.keys(deps), function (dep, cb) {
|
216 | shouldUpdate(args, dir, dep, has, deps[dep], depth, cb)
|
217 | }, cb)
|
218 | }
|
219 | }
|
220 |
|
221 | function shouldUpdate (args, dir, dep, has, req, depth, cb) {
|
222 |
|
223 |
|
224 |
|
225 |
|
226 |
|
227 | var curr = has[dep]
|
228 |
|
229 | function skip (er) {
|
230 |
|
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 |
|
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 |
|
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 |
|
267 |
|
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 |
|
276 | cache.add(dep, req, onCacheAdd)
|
277 |
|
278 | function onCacheAdd(er, d) {
|
279 |
|
280 |
|
281 | if (er) {
|
282 | if (er.code && er.code === 'ETARGET') {
|
283 |
|
284 | return skip(er)
|
285 | }
|
286 | return skip()
|
287 | }
|
288 |
|
289 |
|
290 |
|
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 | }
|