link.js 5.6 KB

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