From 23fab365362e51a328f7d20c8d39766ee7906fad Mon Sep 17 00:00:00 2001 From: isaacs Date: Mon, 18 Apr 2011 17:25:27 -0700 Subject: #817 Update adds missing deps, and downgrades if necessary --- lib/outdated.js | 228 ++++++++++++++++++++++++-------------------------------- lib/update.js | 9 ++- 2 files changed, 106 insertions(+), 131 deletions(-) diff --git a/lib/outdated.js b/lib/outdated.js index 2f3293bb1..9e1e2717a 100644 --- a/lib/outdated.js +++ b/lib/outdated.js @@ -18,8 +18,7 @@ outdated.usage = "npm outdated [ [ ...]]" outdated.completion = require("./utils/completion/installed-deep") -var readInstalled = require("./utils/read-installed") - , path = require("path") +var path = require("path") , fs = require("./utils/graceful-fs") , readJson = require("./utils/read-json") , cache = require("./cache") @@ -27,151 +26,122 @@ var readInstalled = require("./utils/read-installed") , npm = require("../npm") , log = require("./utils/log") , semver = require("semver") + , relativize = require("./utils/relativize") -// outdated(pref) -// deps = pref/package.json dependencies, or {:"*"} -// asyncMap over deps -// if exists and (up to date -// or (not in args and args is not empty)) -// return outdated(prefix/node_modules/d) -// else if (in args or args is empty) -// return [prefix, d] - - -// for each thing in prefix/node_modules/* -// if there's a newer one, report it -// otherwise, check its children -var output function outdated (args, silent, cb) { if (typeof cb !== "function") cb = silent, silent = false - var pref = npm.prefix - if (npm.config.get("global")) pref = path.resolve(pref, "lib") - outdated_(pref, args, function (er, list) { - if (er) return cb(er) - if (list.length && !silent) { - var outList = list.map(function (ww) { - return ww[1] + ": "+ww[3] + (ww[2] ? " (currently: "+ww[2]+")":"") - }) - output = output || require("./utils/output") - output.write(outList.join("\n"), function (e) { - cb(e, list) - }) - } else cb(null, list) + var dir = npm.prefix + if (npm.config.get("global")) dir = path.resolve(dir, "lib") + outdated_(args, dir, {}, function (er, list) { + function cb_ (er) { cb(er, list) } + + if (er || silent) return cb_(er) + var outList = list.map(makePretty) + require("./utils/output").write(outList.join("\n"), cb_) }) } -function outdated_ (prefix, args, cb) { - log(prefix, "outdated_ about to getDeps") - getDeps(prefix, function (er, deps) { - log([prefix, er, deps], "back from getDeps") - if (er) return cb(er) - // now deps is a "dependencies" object, {:} - asyncMap(Object.keys(deps), function (dep, cb) { - var req = deps[dep] - log([prefix, dep, req], "outdated_") - validateDep(prefix, args, dep, req, function (er, exists, needsUpdate) { - if (er) return cb(er) - log([prefix, dep, req, exists, needsUpdate], "outdated_") - var dir = path.resolve(prefix, "node_modules", dep) - if (needsUpdate) return cb(null, [[ prefix - , dep - , exists - , dir ]]) - else if (!exists) return cb(null, []) - else outdated_(path.resolve(prefix, "node_modules", dep), args, cb) - }) - }, cb) - }) -} - -// return cb with (er, currentVersion or false, needsUpdate boolean) -function validateDep (prefix, args, dep, req, cb) { - var canUpdate = args.length === 0 ? true : args.indexOf(dep) !== -1 - , current = -1 - , latest - , needsUpdate = false - - readJson(path.resolve(prefix, "node_modules", dep,"package.json") - ,function (er, data) { - if (er) current = false - else current = data.version - log("back from readJson", "validateDep "+dep) - next() - }) +// [[ dir, dep, has, want ]] +function makePretty (p) { + var parseable = npm.config.get("parseable") + , long = npm.config.get("long") + , dep = p[1] + , dir = path.resolve(p[0], "node_modules", dep) + , has = p[2] + , want = p[3] + if (parseable) { + var str = dir + if (npm.config.get("long")) { + str += ":" + dep + "@" + want + + ":" + (has ? (dep + "@" + has) : "MISSING") + } + return str + } - // only check for newer version if we can update this thing. - if (canUpdate) { - log([dep, req], "about to check") - cache.add(dep, req, function (er, data) { - if (er) { - // ignore 404 errors, since it's probably just that they installed - // something locally. other errors indicate something is wrong. - if (er.errno !== npm.E404) return next(er) - canUpdate = false - return next() - } - latest = data.version - log([dep, req, latest, current], "dep, req, latest, current") - next() - }) - } else next() - - var errState - function next (er) { - log([dep, req, current, latest, canUpdate, er], "validateDep next") - if (errState) return log("error state") - if (er) return cb(errState = er) - if (canUpdate && !latest) return log("waiting for latest") - if (current === -1) return log("waiting for current") - // now we know the current version (or false if it's not there) - // and have the version that it ought to be. - var needsUpdate = canUpdate && semver.gt(latest, current) - cb(null, current, needsUpdate) + if (!npm.config.get("global")) { + dir = relativize(dir, process.cwd()+"/x") } + return dep + "@" + want + " " + dir + + " current=" + (has || "MISSING") } -// deps is the package.json dependencies, -// plus any node_modules/* that aren't in the dependencies. -function getDeps (prefix, cb) { - var jsonDeps - , folderDeps - - readJson(path.resolve(prefix, "package.json"), function (er, data) { - if (er) jsonDeps = {} - else jsonDeps = data.dependencies || {} - next() +function outdated_ (args, dir, parentHas, cb) { + // get the deps from package.json, or {:"*"} + // asyncMap over deps: + // shouldHave = cache.add(dep, req).version + // if has === shouldHave then + // return outdated(args, dir/node_modules/dep, parentHas + has) + // else if dep in args or args is empty + // return [dir, dep, has, shouldHave] + + var deps = null + readJson(path.resolve(dir, "package.json"), function (er, d) { + deps = (er) ? true : d.dependencies + return next() }) - var dir = path.resolve(prefix, "node_modules") - fs.readdir(dir, function (er, list) { - if (er) list = [] - log(list, "readdir") - list = list.filter(function (l) { - return l.charAt(0) !== "." - }) - asyncMap(list, function (l, cb) { - fs.lstat(path.resolve(dir, l), function (er, st) { - if (er) return cb(null, []) - if (!st.isDirectory()) return cb(null, []) - return cb(null, l) + var has = null + fs.readdir(path.resolve(dir, "node_modules"), function (er, pkgs) { + if (er) { + has = Object.create(parentHas) + return next() + } + asyncMap(pkgs, function (pkg, cb) { + readJson( path.resolve(dir, "node_modules", pkg, "package.json") + , function (er, d) { + cb(null, er ? [] : [[d.name, d.version]]) }) - }, function (er, list) { - log(list, "asyncMapped over dir") + }, function (er, pvs) { if (er) return cb(er) - folderDeps = list.reduce(function (l, r) { - l[r] = "*" - return l - }, {}) || {} + has = Object.create(parentHas) + pvs.forEach(function (pv) { + has[pv[0]] = pv[1] + }) next() }) }) function next () { - log([jsonDeps, folderDeps], "getDeps next") - if (!jsonDeps || !folderDeps) return - Object.keys(jsonDeps).forEach(function (d) { - folderDeps[d] = jsonDeps[d] - }) - return cb(null, folderDeps) + if (!has || !deps) return + if (deps === true) { + deps = Object.keys(has).reduce(function (l, r) { + l[r] = "*" + return l + }, {}) + } + // now get what we should have, based on the dep. + // if has[dep] !== shouldHave[dep], then cb with the data + // otherwise dive into the folder + asyncMap(Object.keys(deps), function (dep, cb) { + shouldUpdate(args, dir, dep, has, deps[dep], cb) + }, cb) } } + +function shouldUpdate (args, dir, dep, has, req, cb) { + // look up the most recent version. + // if that's what we already have, or if it's not on the args list, + // then dive into it. Otherwise, cb() with the data. + + function skip () { + outdated_( args + , path.resolve(dir, "node_modules", dep) + , has + , cb ) + } + + function doIt (shouldHave) { + cb(null, [[ dir, dep, has[dep], shouldHave ]]) + } + + if (args.length && args.indexOf(dep) === -1) { + return skip() + } + + // so, we can conceivably update this. find out if we need to. + cache.add(dep, req, function (er, d) { + // if this fails, then it means we can't update this thing. + // it's probably a thing that isn't published. + return (er || d.version === has[dep]) ? skip() : doIt(d.version) + }) +} diff --git a/lib/update.js b/lib/update.js index fd9e963f4..6416435e4 100644 --- a/lib/update.js +++ b/lib/update.js @@ -20,10 +20,15 @@ function update (args, cb) { npm.commands.outdated(args, true, function (er, outdated) { log(outdated, "outdated updating") if (er) return cb(er) + asyncMap(outdated, function (ww, cb) { + // [[ dir, dep, has, want ]] var where = ww[0] - , what = ww[1] - log([where, what], "updating") + , dep = ww[1] + , want = ww[3] + , what = dep + "@" + want + + log.warn([where, what], "updating") npm.commands.install(where, what, cb) }, cb) }) -- cgit v1.2.3