diff options
-rw-r--r-- | lib/update-dependents.js | 214 | ||||
-rw-r--r-- | lib/update.js | 99 | ||||
-rwxr-xr-x | npm.js | 2 |
3 files changed, 315 insertions, 0 deletions
diff --git a/lib/update-dependents.js b/lib/update-dependents.js new file mode 100644 index 000000000..095e240ab --- /dev/null +++ b/lib/update-dependents.js @@ -0,0 +1,214 @@ +/* + +This command is plumbing. + +npm update-deps <pkg> + +For each other version of pkg, for each dependent in other +version's dependents folder, if the new version would satisfy the +dependency as well, update other version's dependent's dependency +links to point at the new version + +If no dependents are left, then remove old version + +*/ + +module.exports = updateDependents + +var readInstalled = require("./utils/read-installed") + , path = require("path") + , npm = require("../npm") + , chain = require("./utils/chain") + , semver = require("./utils/semver") + , link = require("./utils/link") + , linkIfExists = link.ifExists + , shim = require("./utils/write-shim") + , shimIfExists = shim.ifExists + , readJson = require("./utils/read-json") + , log = require("./utils/log") + , fs = require("fs") + , rm = require("./utils/rm-rf") + +function updateDependents (args, cb) { + // replace args with the installed data + if (!args.length) return cb() // nothing to do + readArgs(args, function (er, args) { + if (er) return log.er(cb, "Error reading args")(er) + // now this is an array of package descriptors + // and each one is installed, and has siblings. + // update any dependents on any other versions to this one, + // if it satisfies them, and then remove them if they have + // no more dependents + if (!args.length) return log( + "Nothing to update", "update dependents", cb) + chain(args.map(function (arg) { + return [updateDepsTo, arg] + }).concat(function (er) { + // now they've all been updated, so remove the others. + var rmList = [] + args.forEach(function (arg) { + // if (!arg._others) log(arg); return + arg._others.forEach(function (o) { + rmList.push(arg.name+"@"+o) + }) + }) + npm.commands.rm(rmList, cb) + })) + }) +} + +// update the _others to this one. +function updateDepsTo (arg, cb) { + chain(arg._others.map(function (o) { + return [updateOtherVersionDeps, o, arg] + }).concat(cb)) +} + +function updateOtherVersionDeps (other, pkg, cb) { + var depdir = path.join( npm.dir + , pkg.name + , other + , "dependents" + ) + fs.readdir(depdir, function (er, deps) { + if (er) return log.er(cb, + "failed to read dependents on "+pkg.name+"@"+other)(er) + // for each of these, update the dependency on + // other to pkg + if (!deps.length) return cb() + chain(deps.map(function (d) { + // todo: make this a @ instead of a - + d = d.split("-") + var name = d.shift() + , ver = d.join("-") + return [updateDepToNew, name, ver, pkg, other] + }).concat(cb)) + }) +} +function updateDepToNew (depName, depVer, pkg, other, cb) { + var depdir = path.join(npm.dir, depName, depVer) + , jsonFile = path.join(depdir, "package", "package.json") + readJson(jsonFile, function (er, data) { + if (er) return log.er(cb, "failed to read "+jsonFile)(er) + // check if pkg is ok + var dependencies = data.dependencies + if (!dependencies) return log + ( "Weird, "+depName+"@"+depVer+" doesn't have any dependencies" + , "wtf?" + , cb + ) + if (Array.isArray(dependencies)) { + var deps = {} + dependencies.forEach(function (d) { deps[d] = "*" }) + dependencies = deps + } + var dependency = data.dependencies[pkg.name] + if (!dependency) return log + ( "Weird, "+depName+"@"+depVer+" doesn't depend on "+pkg.name + , "wtf?" + , cb + ) + if (!semver.satisfies(pkg.version, dependency)) return log + ( pkg._id + " doesn't satisfy "+depName+"@"+depVer + , "not updating" + , cb + ) + + chain + ( [ removeDependencyLinks, data, pkg, other ] + , [ createDependencyLinks, data, pkg ] + , cb + ) + }) +} + +function removeDependencyLinks (dep, pkg, other, cb) { + var depdir = path.join(npm.dir, dep.name, dep.version) + , depsOn = path.join(depdir, "dependson", pkg.name+"-"+other) + , deps = path.join(depdir, "dependencies", pkg.name) + , dependentLink = path.join( npm.dir + , pkg.name + , other + , "dependents" + , dep.name + "-" + dep.version + ) + chain + ( [ rm, deps+".js" ] + , [ rm, deps ] + , [ rm, depsOn ] + , [ rm, dependentLink ] + , cb + ) +} +function createDependencyLinks (dep, pkg, cb) { + var depdir = path.join(npm.dir, dep.name, dep.version) + , depsOn = path.join( depdir + , "dependson" + , pkg.name+"-"+pkg.version + ) + , deps = path.join(depdir, "dependencies", pkg.name) + , targetRoot = path.join(npm.dir, pkg.name, pkg.version) + , targetMain = path.join(targetRoot, "main.js") + , targetLib = path.join(targetRoot, "lib") + , dependentLink = path.join( npm.dir + , pkg.name + , pkg.version + , "dependents" + , dep.name + "-" + dep.version + ) + chain + ( [ link, targetRoot, depsOn ] + , [ link, depdir, dependentLink ] + , [ linkIfExists, targetLib, deps ] + , [ shimIfExists, targetMain, deps + ".js" ] + , cb + ) +} + +function readArgs (args, cb) { + var p = args.length + log(args, "readArgs before") + function r () { if (--p === 0) { + log(args, "readArgs after") + cb(null, args.filter(function (a) { return a })) + }} + function readOthers (arg, i) { + log(arg, "readOthers") + readInstalled([arg.name], function (er, inst) { + log(inst, "installed "+arg.name) + if (er) { + args[i] = null + return log(er, "Error reading installed", r) + } + var have = Object.keys(inst[arg.name]) + if (have.length < 2) { + args[i] = null + return log( + "Only one version installed", "update-dependents "+arg.name, r) + } + arg._others = have.filter(function (v) { return v !== arg.version }) + r() + }) + } + + args.forEach(function (arg, i) { + if (typeof arg === "object") return readOthers(arg, i) + arg = arg.split(/@/) + var name = arg.shift() + , ver = arg.join("@") + , jsonFile = path.join( npm.dir + , name + , ver + , "package" + , "package.json" + ) + readJson(jsonFile, function (er, arg) { + if (er) { + args[i] = null + return log(er, "Error reading "+jsonFile, r) + } + args[i] = arg + readOthers(arg, i) + }) + }) +} diff --git a/lib/update.js b/lib/update.js new file mode 100644 index 000000000..27d642afc --- /dev/null +++ b/lib/update.js @@ -0,0 +1,99 @@ +/* +http://github.com/isaacs/npm/issues/issue/7 + +npm update [pkg] + +Does the following: + +1. check for a new version of pkg +2. if not found, then quit +3. install new version of pkg +4. For each other version of pkg, for each dependent in + other version's dependents folder, if the new version + would satisfy the dependency as well, update other + version's dependent's dependency links to point at the + new version +5. If no dependents are left, then remove old version + +If no packages are specified, then run for all installed +packages. + +Depending on config value, auto-update, run steps 4-5 +after installation + +* always - Run an update after every install, so as to + minimize the different number of versions of things. +* true - Default, run if newly installed version is + the highest version number (that is, don't downgrade + by default) +* false - Don't run "update" automatically after + installation. + +*/ + +module.exports = update + +var readInstalled = require("./utils/read-installed") + , chain = require("./utils/chain") + , log = require("./utils/log") + , registry = require("./utils/registry") + , npm = require("../npm") + , semver = require("./utils/semver") + +function update (args, cb) { + findUpdates(args, function (er, updates) { + if (er) return log.er(cb, "failed to find updates")(er) + if (!updates || Object.keys(updates).length === 0) return log( + "Nothing to update", "update", cb) + installUpdates(updates, cb) + }) +} +function installUpdates (updates, cb) { + log(updates, "install updates") + npm.config.set("auto-update", true) + var installList = [] + , updateList = [] + Object.keys(updates).forEach(function (i) { + var u = updates[i] + if (u.have.indexOf(u.latest) === -1) { + installList.push(i+"@"+u.latest) + } else { + updateList.push(i+"@"+u.latest) + } + }) + log(installList, "update installList") + log(updateList, "update updateList") + npm.commands.install(installList, function (er) { + if (er) return log.er(cb, "install failed "+installList)(er) + npm.commands["update-dependents"](updateList, function (er) { + if (er) return log.er(cb, "update failed "+updateList)(er) + cb() + }) + }) +} + +function findUpdates (args, cb) { + log(args, "findUpdates") + readInstalled(args, function (er, inst) { + if (er) return log.er(cb, "Couldn't read installed packages")(er) + log(inst, "installed") + var pkgs = Object.keys(inst) + , p = pkgs.length + , updates = {} + , tag = npm.config.get("tag") + function found () { if (--p === 0) cb(null, updates) } + pkgs.forEach(function (pkg) { + registry.get(pkg, function (er, data) { + if (er) return log(pkg, "not in registry", found) + var latest = data["dist-tags"] && data["dist-tags"][tag] + , have = Object.keys(inst[pkg]).sort(semver.sort) + , minHave = have[0] + if (!latest || !semver.gt(latest, minHave)) return found() + // we have something that's out of date. + updates[pkg] = {latest:latest,have:have} + found() + }) + }) + }) +} + @@ -47,6 +47,8 @@ var commandCache = {} , "ls" , "rm" , "owner" + , "update" + , "update-dependents" ].forEach(function (c) { Object.defineProperty(npm.commands, c, { get : function () { c = c === "list" ? "ls" |