UNPKG

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