index.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. 'use strict';
  2. function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
  3. var fs = require('fs');
  4. var path = require('path');
  5. var which = require('which');
  6. var findPrefix = require('./find-prefix');
  7. var PATH = getPATHKey();
  8. var SEPARATOR = getPATHSeparator();
  9. /**
  10. * Get new $PATH setting with additional paths supplied by the npm.
  11. *
  12. * @param Object options Config options Object.
  13. * @param Object options.env Environment to use. Default: process.env
  14. * @param String options.wd Working directory. Default: process.cwd()
  15. * @param Function fn callback function.
  16. */
  17. function getPath(options, fn) {
  18. options.cwd = options.cwd || process.cwd();
  19. var env = options.env = options.env || process.env;
  20. var pathArr = getPathArr(options);
  21. findPrefix(options, function (err, prefixPath) {
  22. if (!err && prefixPath) {
  23. // ignore err if cannot find prefix
  24. pathArr.unshift(path.join(prefixPath, 'node_modules', '.bin'));
  25. }
  26. whichNpm(options, function (err, npmPath) {
  27. // we also unshift the bundled node-gyp-bin folder so that
  28. // the bundled one will be used for installing things.
  29. // simply ignore this step if there was no npm found
  30. if (err || !npmPath) {
  31. // ...unless npm path was explicitly passed in
  32. if (options.npm) {
  33. return fn(err || new Error('Cannot find ' + options.npm));
  34. }
  35. } else {
  36. pathArr.unshift(path.join(path.dirname(npmPath), 'node-gyp-bin'));
  37. }
  38. if (env[PATH]) pathArr = pathArr.concat(env[PATH].split(SEPARATOR));
  39. // Remove duplicated entries
  40. pathArr = [].concat(_toConsumableArray(new Set(pathArr)));
  41. fn(null, pathArr.join(SEPARATOR));
  42. });
  43. });
  44. }
  45. /**
  46. * Async wrapper around `getPath`.
  47. */
  48. function getPathAsync(options, fn) {
  49. // options is optional
  50. if (typeof options === 'function') {
  51. fn = options;
  52. options = {};
  53. }
  54. // if no fn, execute as sync
  55. if (typeof fn !== 'function') return getPathSync(options);
  56. options = options || {};
  57. options.isSync = false;
  58. return getPath(options, fn);
  59. }
  60. /**
  61. * Sync wrapper around `getPath`.
  62. */
  63. function getPathSync(options) {
  64. options = options || {};
  65. options.isSync = true;
  66. var thePath = null;
  67. // sync magic: if sync true, callback is executed sync
  68. // therefore we can set thePath from inside it before returning
  69. getPath(options, function (err, foundPath) {
  70. if (err) throw err;
  71. thePath = foundPath;
  72. });
  73. return thePath;
  74. }
  75. /**
  76. * Change environment to include npm path adjustments.
  77. *
  78. * @param Object options Config options Object.
  79. * @param Object options.env Environment to use. Default: process.env
  80. * @param String options.wd Working directory. Default: process.cwd()
  81. * @param Function fn callback function.
  82. */
  83. function setPathAsync(options, fn) {
  84. // options is optional
  85. if (typeof options === 'function') {
  86. fn = options;
  87. options = {};
  88. }
  89. // if no fn, execute as sync
  90. if (typeof fn !== 'function') return setPathSync(options);
  91. getPathAsync(options, function (err, newPath) {
  92. if (err) return fn(err);
  93. fn(null, options.env[PATH] = newPath);
  94. });
  95. }
  96. /**
  97. * Sync version of `setPathAsync`
  98. */
  99. function setPathSync(options) {
  100. options = options || {};
  101. var newPath = getPathSync(options);
  102. options.env[PATH] = newPath;
  103. return newPath;
  104. }
  105. /**
  106. * Generate simple parts of the npm path. Basically everything that doesn't
  107. * depend on potentially async operations.
  108. *
  109. * @return Array
  110. */
  111. function getPathArr(options) {
  112. var wd = options.cwd;
  113. var pathArr = [];
  114. var p = wd.split(path.sep + 'node_modules' + path.sep);
  115. var acc = path.resolve(p.shift());
  116. // first add the directory containing the `node` executable currently
  117. // running, so that any lifecycle script that invoke 'node' will execute
  118. // this same one.
  119. pathArr.unshift(path.dirname(process.execPath));
  120. p.forEach(function (pp) {
  121. pathArr.unshift(path.join(acc, 'node_modules', '.bin'));
  122. acc = path.join(acc, 'node_modules', pp);
  123. });
  124. pathArr.unshift(path.join(acc, 'node_modules', '.bin'));
  125. return pathArr;
  126. }
  127. /**
  128. * Use callback-style signature but toggle sync execution if `isSync` is true.
  129. * If options.npm is supplied, this will simply provide npm/bin/npm-cli.
  130. */
  131. function whichNpm(options, fn) {
  132. var npmCli = options.npm && path.join(options.npm, 'bin', 'npm-cli.js');
  133. if (options.isSync) {
  134. var npmPath = null;
  135. try {
  136. npmPath = fs.realpathSync(npmCli || which.sync('npm'));
  137. } catch (err) {
  138. return fn(err);
  139. }
  140. fn(null, npmPath);
  141. return;
  142. }
  143. if (options.npm) {
  144. fs.realpath(npmCli, fn);
  145. return;
  146. }
  147. which('npm', function (err, npmPath) {
  148. if (err) return fn(err);
  149. fs.realpath(npmPath, fn);
  150. });
  151. }
  152. /**
  153. * Get key to use as $PATH in environment
  154. */
  155. function getPATHKey() {
  156. var PATH = 'PATH';
  157. // windows calls it's path 'Path' usually, but this is not guaranteed.
  158. if (process.platform === 'win32') {
  159. PATH = 'Path';
  160. Object.keys(process.env).forEach(function (e) {
  161. if (e.match(/^PATH$/i)) {
  162. PATH = e;
  163. }
  164. });
  165. }
  166. return PATH;
  167. }
  168. /**
  169. * Get $PATH separator based on environment
  170. */
  171. function getPATHSeparator() {
  172. return process.platform === 'win32' ? ';' : ':';
  173. }
  174. module.exports = setPathAsync;
  175. module.exports.get = getPathAsync;
  176. module.exports.get.sync = getPathSync;
  177. module.exports.getSync = getPathSync;
  178. module.exports.set = setPathAsync;
  179. module.exports.set.sync = setPathSync;
  180. module.exports.setSync = setPathSync;
  181. module.exports.PATH = PATH;
  182. module.exports.SEPARATOR = SEPARATOR;