UNPKG

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