1 // link with no args: symlink the folder to the global location
2 // link with package arg: symlink the global to the local
4 var npm = require("./npm.js")
5 , symlink = require("./utils/link.js")
6 , fs = require("graceful-fs")
7 , log = require("npmlog")
8 , asyncMap = require("slide").asyncMap
9 , chain = require("slide").chain
10 , path = require("path")
11 , build = require("./build.js")
12 , npa = require("npm-package-arg")
16 link.usage = "npm link (in package dir)"
17 + "\nnpm link <pkg> (link global into local)"
19 link.completion = function (opts, cb) {
20 var dir = npm.globalDir
21 fs.readdir(dir, function (er, files) {
22 cb(er, files.filter(function (f) {
23 return !f.match(/^[\._-]/)
28 function link (args, cb) {
29 if (process.platform === 'win32') {
30 var semver = require('semver')
31 if (!semver.gte(process.version, '0.7.9')) {
32 var msg = 'npm link not supported on windows prior to node 0.7.9'
33 var e = new Error(msg)
35 e.errno = require('constants').ENOTSUP
40 if (npm.config.get("global")) {
41 return cb(new Error("link should never be --global.\n"
42 +"Please re-run this command with --local"))
45 if (args.length === 1 && args[0] === ".") args = []
46 if (args.length) return linkInstall(args, cb)
47 linkPkg(npm.prefix, cb)
50 function linkInstall (pkgs, cb) {
51 asyncMap(pkgs, function (pkg, cb) {
52 var t = path.resolve(npm.globalDir, "..")
53 , pp = path.resolve(npm.globalDir, pkg)
55 , target = path.resolve(npm.dir, pkg)
57 function n (er, data) {
58 if (er) return cb(er, data)
59 // install returns [ [folder, pkgId], ... ]
60 // but we definitely installed just one thing.
61 var d = data.filter(function (d) { return !d[3] })
62 var what = npa(d[0][0])
65 target = path.resolve(npm.dir, pkg)
69 // if it's a folder, a random not-installed thing, or not a scoped package,
70 // then link or install it first
71 if (pkg[0] !== "@" && (pkg.indexOf("/") !== -1 || pkg.indexOf("\\") !== -1)) {
72 return fs.lstat(path.resolve(pkg), function (er, st) {
73 if (er || !st.isDirectory()) {
74 npm.commands.install(t, pkg, n)
76 rp = path.resolve(pkg)
82 fs.lstat(pp, function (er, st) {
85 return npm.commands.install(t, pkg, n)
86 } else if (!st.isSymbolicLink()) {
90 return fs.realpath(pp, function (er, real) {
91 if (er) log.warn("invalid symbolic link", pkg)
101 log.verbose("link", "symlinking %s to %s", pp, target)
104 , [symlink, pp, target]
105 // do not run any scripts
106 , rp && [build, [target], npm.config.get("global"), build._noLC, true]
107 , [ resultPrinter, pkg, pp, target, rp ] ]
113 function linkPkg (folder, cb_) {
114 var me = folder || npm.prefix
115 , readJson = require("read-package-json")
117 log.verbose("linkPkg", folder)
119 readJson(path.resolve(me, "package.json"), function (er, d) {
121 return cb_(er, [[d && d._id, target, null, null]])
123 if (er) return cb(er)
125 er = new Error("Package must have a name field to be linked")
128 var target = path.resolve(npm.globalDir, d.name)
129 symlink(me, target, false, true, function (er) {
130 if (er) return cb(er)
131 log.verbose("link", "build target", target)
132 // also install missing dependencies.
133 npm.commands.install(me, [], function (er) {
134 if (er) return cb(er)
135 // build the global stuff. Don't run *any* scripts, because
136 // install command already will have done that.
137 build([target], true, build._noLC, true, function (er) {
138 if (er) return cb(er)
139 resultPrinter(path.basename(me), me, target, cb)
146 function resultPrinter (pkg, src, dest, rp, cb) {
147 if (typeof cb !== "function") cb = rp, rp = null
149 rp = (rp || "").trim()
150 src = (src || "").trim()
151 // XXX If --json is set, then look up the data from the package.json
152 if (npm.config.get("parseable")) {
153 return parseableOutput(dest, rp || src, cb)
155 if (rp === src) rp = null
156 console.log(where + " -> " + src + (rp ? " -> " + rp: ""))
160 function parseableOutput (dest, rp, cb) {
161 // XXX this should match ls --parseable and install --parseable
162 // look up the data from package.json, format it the same way.
164 // link is always effectively "long", since it doesn't help much to
165 // *just* print the target folder.
166 // However, we don't actually ever read the version number, so
167 // the second field is always blank.
168 console.log(dest + "::" + rp)