| // link with no args: symlink the folder to the global location |
| // link with package arg: symlink the global to the local |
| |
| var npm = require("./npm.js") |
| , symlink = require("./utils/link.js") |
| , fs = require("graceful-fs") |
| , log = require("npmlog") |
| , asyncMap = require("slide").asyncMap |
| , chain = require("slide").chain |
| , path = require("path") |
| , build = require("./build.js") |
| , npa = require("npm-package-arg") |
| |
| module.exports = link |
| |
| link.usage = "npm link (in package dir)" |
| + "\nnpm link <pkg> (link global into local)" |
| |
| link.completion = function (opts, cb) { |
| var dir = npm.globalDir |
| fs.readdir(dir, function (er, files) { |
| cb(er, files.filter(function (f) { |
| return !f.match(/^[\._-]/) |
| })) |
| }) |
| } |
| |
| function link (args, cb) { |
| if (process.platform === 'win32') { |
| var semver = require('semver') |
| if (!semver.gte(process.version, '0.7.9')) { |
| var msg = 'npm link not supported on windows prior to node 0.7.9' |
| var e = new Error(msg) |
| e.code = 'ENOTSUP' |
| e.errno = require('constants').ENOTSUP |
| return cb(e) |
| } |
| } |
| |
| if (npm.config.get("global")) { |
| return cb(new Error("link should never be --global.\n" |
| +"Please re-run this command with --local")) |
| } |
| |
| if (args.length === 1 && args[0] === ".") args = [] |
| if (args.length) return linkInstall(args, cb) |
| linkPkg(npm.prefix, cb) |
| } |
| |
| function linkInstall (pkgs, cb) { |
| asyncMap(pkgs, function (pkg, cb) { |
| var t = path.resolve(npm.globalDir, "..") |
| , pp = path.resolve(npm.globalDir, pkg) |
| , rp = null |
| , target = path.resolve(npm.dir, pkg) |
| |
| function n (er, data) { |
| if (er) return cb(er, data) |
| // install returns [ [folder, pkgId], ... ] |
| // but we definitely installed just one thing. |
| var d = data.filter(function (d) { return !d[3] }) |
| var what = npa(d[0][0]) |
| pp = d[0][1] |
| pkg = what.name |
| target = path.resolve(npm.dir, pkg) |
| next() |
| } |
| |
| // if it's a folder, a random not-installed thing, or not a scoped package, |
| // then link or install it first |
| if (pkg[0] !== "@" && (pkg.indexOf("/") !== -1 || pkg.indexOf("\\") !== -1)) { |
| return fs.lstat(path.resolve(pkg), function (er, st) { |
| if (er || !st.isDirectory()) { |
| npm.commands.install(t, pkg, n) |
| } else { |
| rp = path.resolve(pkg) |
| linkPkg(rp, n) |
| } |
| }) |
| } |
| |
| fs.lstat(pp, function (er, st) { |
| if (er) { |
| rp = pp |
| return npm.commands.install(t, pkg, n) |
| } else if (!st.isSymbolicLink()) { |
| rp = pp |
| next() |
| } else { |
| return fs.realpath(pp, function (er, real) { |
| if (er) log.warn("invalid symbolic link", pkg) |
| else rp = real |
| next() |
| }) |
| } |
| }) |
| |
| function next () { |
| chain |
| ( [ [function (cb) { |
| log.verbose("link", "symlinking %s to %s", pp, target) |
| cb() |
| }] |
| , [symlink, pp, target] |
| // do not run any scripts |
| , rp && [build, [target], npm.config.get("global"), build._noLC, true] |
| , [ resultPrinter, pkg, pp, target, rp ] ] |
| , cb ) |
| } |
| }, cb) |
| } |
| |
| function linkPkg (folder, cb_) { |
| var me = folder || npm.prefix |
| , readJson = require("read-package-json") |
| |
| log.verbose("linkPkg", folder) |
| |
| readJson(path.resolve(me, "package.json"), function (er, d) { |
| function cb (er) { |
| return cb_(er, [[d && d._id, target, null, null]]) |
| } |
| if (er) return cb(er) |
| if (!d.name) { |
| er = new Error("Package must have a name field to be linked") |
| return cb(er) |
| } |
| var target = path.resolve(npm.globalDir, d.name) |
| symlink(me, target, false, true, function (er) { |
| if (er) return cb(er) |
| log.verbose("link", "build target", target) |
| // also install missing dependencies. |
| npm.commands.install(me, [], function (er) { |
| if (er) return cb(er) |
| // build the global stuff. Don't run *any* scripts, because |
| // install command already will have done that. |
| build([target], true, build._noLC, true, function (er) { |
| if (er) return cb(er) |
| resultPrinter(path.basename(me), me, target, cb) |
| }) |
| }) |
| }) |
| }) |
| } |
| |
| function resultPrinter (pkg, src, dest, rp, cb) { |
| if (typeof cb !== "function") cb = rp, rp = null |
| var where = dest |
| rp = (rp || "").trim() |
| src = (src || "").trim() |
| // XXX If --json is set, then look up the data from the package.json |
| if (npm.config.get("parseable")) { |
| return parseableOutput(dest, rp || src, cb) |
| } |
| if (rp === src) rp = null |
| console.log(where + " -> " + src + (rp ? " -> " + rp: "")) |
| cb() |
| } |
| |
| function parseableOutput (dest, rp, cb) { |
| // XXX this should match ls --parseable and install --parseable |
| // look up the data from package.json, format it the same way. |
| // |
| // link is always effectively "long", since it doesn't help much to |
| // *just* print the target folder. |
| // However, we don't actually ever read the version number, so |
| // the second field is always blank. |
| console.log(dest + "::" + rp) |
| cb() |
| } |