index.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. "use strict";
  2. // https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders
  3. const childProcess = require("child_process"),
  4. path = require("path"),
  5. fs = require("fs"),
  6. processWrapper = require("./process-wrapper");
  7. const supportedPlatforms = [
  8. "win32",
  9. "linux",
  10. "darwin"
  11. ];
  12. const nodeModulesDirName = "node_modules";
  13. const getNpmExecutable = (platform) => {
  14. let npmExecutableName = "npm";
  15. if (platform === "win32") {
  16. npmExecutableName += ".cmd";
  17. }
  18. return npmExecutableName;
  19. };
  20. const getNpmPrefix = (pathToNpm) => {
  21. try {
  22. const npmPrefixStdout = childProcess.execSync(`${pathToNpm} config get prefix`);
  23. return npmPrefixStdout && npmPrefixStdout.toString().trim();
  24. } catch (err) {
  25. console.error(err.message);
  26. }
  27. return null;
  28. };
  29. const getPathFromNpmConfig = (platform, packageName) => {
  30. const pathToNpm = getNpmExecutable(platform),
  31. npmConfigPrefix = getNpmPrefix(pathToNpm);
  32. if (npmConfigPrefix) {
  33. let nodeModulesPath = path.join(npmConfigPrefix, nodeModulesDirName);
  34. if (platform !== "win32") {
  35. nodeModulesPath = path.join(npmConfigPrefix, "lib", nodeModulesDirName);
  36. }
  37. const packagePath = path.join(nodeModulesPath, packageName);
  38. const verifiedPath = getVerifiedPath(packagePath, packageName);
  39. if (verifiedPath) {
  40. return verifiedPath;
  41. }
  42. }
  43. return null;
  44. };
  45. const getPathFromCmdContent = (packageName, pathToExecutable) => {
  46. if (fs.existsSync(pathToExecutable)) {
  47. const executableContent = fs.readFileSync(pathToExecutable).toString();
  48. let fullPath;
  49. let windowsPathRegExp = /(%~dp0[\w\\.-]+node_modules).*?"/g;
  50. let match = windowsPathRegExp.exec(executableContent);
  51. if (match && match[1]) {
  52. const realPath = path.normalize(match[1].replace("%~dp0", path.dirname(pathToExecutable)));
  53. fullPath = path.join(realPath, packageName);
  54. }
  55. if (!fullPath) {
  56. windowsPathRegExp = new RegExp(`(%~dp0[\\w\\\\.-]+?${packageName})(?:\\\\|")`, "g");
  57. match = windowsPathRegExp.exec(executableContent);
  58. if (match && match[1]) {
  59. fullPath = path.normalize(match[1].replace("%~dp0", path.dirname(pathToExecutable)));
  60. }
  61. }
  62. if (fullPath) {
  63. const pathToPackage = getVerifiedPath(fullPath, packageName);
  64. if (pathToPackage) {
  65. return pathToPackage;
  66. }
  67. }
  68. }
  69. };
  70. const getVerifiedPath = (suggestedPath, packageName) => {
  71. const pathToPackageJson = path.join(suggestedPath, "package.json");
  72. if (fs.existsSync(suggestedPath) && fs.existsSync(pathToPackageJson)) {
  73. try {
  74. const packageJsonContent = JSON.parse(fs.readFileSync(pathToPackageJson));
  75. if (packageJsonContent.name === packageName) {
  76. return suggestedPath;
  77. }
  78. } catch (err) {
  79. // do nothing
  80. }
  81. }
  82. };
  83. const getPathFromExecutableNameOnWindows = (packageName, executableName) => {
  84. try {
  85. const whereResult = (childProcess.execSync(`where ${executableName}`) || "").toString().split("\n");
  86. for (const line of whereResult) {
  87. const pathToExecutable = line && line.trim();
  88. if (pathToExecutable) {
  89. const pathToLib = path.join(path.dirname(pathToExecutable), nodeModulesDirName, packageName);
  90. const verifiedPath = getVerifiedPath(pathToLib, packageName);
  91. if (verifiedPath) {
  92. return verifiedPath;
  93. }
  94. // consider checking the content of the file - in most of the cases it contains the real path to the executable.
  95. const pathToExecutableFromContent = getPathFromCmdContent(packageName, pathToExecutable);
  96. if (pathToExecutableFromContent) {
  97. return pathToExecutableFromContent;
  98. }
  99. // In case the path to <package>/bin/ is added to the PATH
  100. const resolvedPath = getPathWhenExecutableIsAddedDirectlyToPath(packageName, pathToExecutable);
  101. if (resolvedPath) {
  102. return resolvedPath;
  103. }
  104. }
  105. }
  106. } catch (err) {
  107. console.error(err.message);
  108. }
  109. return null;
  110. };
  111. const getPathFromExecutableNameOnNonWindows = (packageName, executableName) => {
  112. try {
  113. // Second way to find it is to use the result of which command
  114. // It will give path to the executable, which is a symlink in fact, so we can get the full path from it:
  115. // whichResult: /usr/local/nvm/versions/node/v4.2.1/bin/mobile-cli-lib
  116. // lsLResult: lrwxrwxrwx 1 rvladimirov rvladimirov 52 Oct 20 14:51 /usr/local/nvm/versions/node/v4.2.1/bin/mobile-cli-lib -> ../lib/node_modules/mobile-cli-lib/bin/common-lib.js
  117. const whichResult = (childProcess.execSync(`which ${executableName}`) || "").toString().trim(),
  118. lsLResult = (childProcess.execSync(`ls -l \`which ${executableName}\``) || "").toString().trim();
  119. if (whichResult && lsLResult) {
  120. const regex = new RegExp(`${whichResult}\\s+->\\s+(.*?)$`),
  121. match = lsLResult.match(regex);
  122. if (match && match[1]) {
  123. const pathToRealExecutable = fs.realpathSync(path.join(path.dirname(whichResult), match[1]));
  124. // The executable is somewhere inside node_modules/<package name>/ directory,
  125. // so after we have the full path to executable, we are safe to match the path to node_modules/<package name>/ from it - that's where our module is.
  126. const packagePathMatch = pathToRealExecutable.match(new RegExp(`(.*?${path.join(nodeModulesDirName, packageName)}).*$`));
  127. if (packagePathMatch) {
  128. const verifiedPath = getVerifiedPath(packagePathMatch[1], packageName);
  129. if (verifiedPath) {
  130. return verifiedPath;
  131. }
  132. }
  133. }
  134. // In case executable is added to PATH directly
  135. return getPathWhenExecutableIsAddedDirectlyToPath(packageName, whichResult);
  136. }
  137. } catch (err) {
  138. console.error(err.message);
  139. }
  140. return null;
  141. };
  142. const getPathWhenExecutableIsAddedDirectlyToPath = (packageName, executablePath) => {
  143. const pathToPackageJson = path.join(path.dirname(executablePath), "..", "package.json");
  144. if (fs.existsSync(pathToPackageJson)) {
  145. const packageNameFromPackageJson = JSON.parse(fs.readFileSync(pathToPackageJson)).name;
  146. if (packageNameFromPackageJson === packageName) {
  147. return path.dirname(pathToPackageJson);
  148. }
  149. }
  150. return null;
  151. };
  152. // For some packages executable name is not the same as package name
  153. // For example our package is called nativescript, but the executable name is called "tns"
  154. const getPath = (packageName, executableName) => {
  155. const platform = processWrapper.getProcessPlatform();
  156. if (supportedPlatforms.indexOf(platform) === -1) {
  157. throw new Error(`OS '${platform}' is not supported.'`);
  158. }
  159. let foundPath = null;
  160. if (executableName) {
  161. foundPath = platform === "win32" ?
  162. getPathFromExecutableNameOnWindows(packageName, executableName) :
  163. getPathFromExecutableNameOnNonWindows(packageName, executableName);
  164. }
  165. if (!foundPath) {
  166. foundPath = getPathFromNpmConfig(platform, packageName);
  167. }
  168. if (foundPath) {
  169. try {
  170. foundPath = fs.realpathSync(foundPath);
  171. } catch (err) {
  172. console.error(err.message);
  173. }
  174. }
  175. return foundPath;
  176. };
  177. module.exports = {
  178. getPath: getPath
  179. };