UNPKG

5.19 kBJavaScriptView Raw
1// link with no args: symlink the folder to the global location
2// link with package arg: symlink the global to the local
3
4var 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 , rm = require("./utils/gently-rm.js")
12 , build = require("./build.js")
13
14module.exports = link
15
16link.usage = "npm link (in package dir)"
17 + "\nnpm link <pkg> (link global into local)"
18
19link.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(/^[\._-]/)
24 }))
25 })
26}
27
28function link (args, cb) {
29 if (process.platform === "win32") {
30 var semver = require("semver")
31 if (!semver.satisfies(process.version, ">=0.7.9")) {
32 var msg = "npm link not supported on windows prior to node 0.7.9"
33 , e = new Error(msg)
34 e.code = "ENOTSUP"
35 e.errno = require("constants").ENOTSUP
36 return cb(e)
37 }
38 }
39
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"))
43 }
44
45 if (args.length === 1 && args[0] === ".") args = []
46 if (args.length) return linkInstall(args, cb)
47 linkPkg(npm.prefix, cb)
48}
49
50function linkInstall (pkgs, cb) {
51 asyncMap(pkgs, function (pkg, cb) {
52 function n (er, data) {
53 if (er) return cb(er, data)
54 // install returns [ [folder, pkgId], ... ]
55 // but we definitely installed just one thing.
56 var d = data.filter(function (d) { return !d[3] })
57 pp = d[0][1]
58 pkg = path.basename(pp)
59 target = path.resolve(npm.dir, pkg)
60 next()
61 }
62
63 var t = path.resolve(npm.globalDir, "..")
64 , pp = path.resolve(npm.globalDir, pkg)
65 , rp = null
66 , target = path.resolve(npm.dir, pkg)
67
68 // if it's a folder or a random not-installed thing, then
69 // link or install it first
70 if (pkg.indexOf("/") !== -1 || pkg.indexOf("\\") !== -1) {
71 return fs.lstat(path.resolve(pkg), function (er, st) {
72 if (er || !st.isDirectory()) {
73 npm.commands.install(t, pkg, n)
74 } else {
75 rp = path.resolve(pkg)
76 linkPkg(rp, n)
77 }
78 })
79 }
80
81 fs.lstat(pp, function (er, st) {
82 if (er) {
83 rp = pp
84 return npm.commands.install(t, pkg, n)
85 } else if (!st.isSymbolicLink()) {
86 rp = pp
87 next()
88 } else {
89 return fs.realpath(pp, function (er, real) {
90 if (er) log.warn("invalid symbolic link", pkg)
91 else rp = real
92 next()
93 })
94 }
95 })
96
97 function next () {
98 chain
99 ( [ [npm.commands, "unbuild", [target]]
100 , [function (cb) {
101 log.verbose("link", "symlinking %s to %s", pp, target)
102 cb()
103 }]
104 , [symlink, pp, target]
105 // do run lifecycle scripts - full build here.
106 , rp && [build, [target]]
107 , [ resultPrinter, pkg, pp, target, rp ] ]
108 , cb )
109 }
110 }, cb)
111}
112
113function linkPkg (folder, cb_) {
114 var me = folder || npm.prefix
115 , readJson = require("read-package-json")
116
117 log.verbose("linkPkg", folder)
118
119 readJson(path.resolve(me, "package.json"), function (er, d) {
120 function cb (er) {
121 return cb_(er, [[d && d._id, target, null, null]])
122 }
123 if (er) return cb(er)
124 if (!d.name) {
125 er = new Error("Package must have a name field to be linked")
126 return cb(er)
127 }
128 var target = path.resolve(npm.globalDir, d.name)
129 rm(target, function (er) {
130 if (er) return cb(er)
131 symlink(me, target, function (er) {
132 if (er) return cb(er)
133 log.verbose("link", "build target", target)
134 // also install missing dependencies.
135 npm.commands.install(me, [], function (er, installed) {
136 if (er) return cb(er)
137 // build the global stuff. Don't run *any* scripts, because
138 // install command already will have done that.
139 build([target], true, build._noLC, true, function (er) {
140 if (er) return cb(er)
141 resultPrinter(path.basename(me), me, target, cb)
142 })
143 })
144 })
145 })
146 })
147}
148
149function resultPrinter (pkg, src, dest, rp, cb) {
150 if (typeof cb !== "function") cb = rp, rp = null
151 var where = dest
152 rp = (rp || "").trim()
153 src = (src || "").trim()
154 // XXX If --json is set, then look up the data from the package.json
155 if (npm.config.get("parseable")) {
156 return parseableOutput(dest, rp || src, cb)
157 }
158 if (rp === src) rp = null
159 console.log(where + " -> " + src + (rp ? " -> " + rp: ""))
160 cb()
161}
162
163function parseableOutput (dest, rp, cb) {
164 // XXX this should match ls --parseable and install --parseable
165 // look up the data from package.json, format it the same way.
166 //
167 // link is always effectively "long", since it doesn't help much to
168 // *just* print the target folder.
169 // However, we don't actually ever read the version number, so
170 // the second field is always blank.
171 console.log(dest + "::" + rp)
172 cb()
173}