npm-install.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. "use strict";
  2. const fs = require("fs-extra");
  3. const log = require("npmlog");
  4. const npa = require("npm-package-arg");
  5. const onExit = require("signal-exit");
  6. const writePkg = require("write-pkg");
  7. const childProcess = require("@lerna/child-process");
  8. const { getNpmExecOpts } = require("@lerna/get-npm-exec-opts");
  9. module.exports.npmInstall = npmInstall;
  10. module.exports.npmInstallDependencies = npmInstallDependencies;
  11. function npmInstall(
  12. pkg,
  13. { registry, npmClient, npmClientArgs, npmGlobalStyle, mutex, stdio = "pipe", subCommand = "install" }
  14. ) {
  15. // build command, arguments, and options
  16. const opts = getNpmExecOpts(pkg, registry);
  17. const args = [subCommand];
  18. let cmd = npmClient || "npm";
  19. if (npmGlobalStyle) {
  20. cmd = "npm";
  21. args.push("--global-style");
  22. }
  23. if (cmd === "yarn" && mutex) {
  24. args.push("--mutex", mutex);
  25. }
  26. if (cmd === "yarn") {
  27. args.push("--non-interactive");
  28. }
  29. if (npmClientArgs && npmClientArgs.length) {
  30. args.push(...npmClientArgs);
  31. }
  32. // potential override, e.g. "inherit" in root-only bootstrap
  33. opts.stdio = stdio;
  34. // provide env sentinels to avoid recursive execution from scripts
  35. opts.env.LERNA_EXEC_PATH = pkg.location;
  36. opts.env.LERNA_ROOT_PATH = pkg.rootPath;
  37. log.silly("npmInstall", [cmd, args]);
  38. return childProcess.exec(cmd, args, opts);
  39. }
  40. function npmInstallDependencies(pkg, dependencies, config) {
  41. log.silly("npmInstallDependencies", pkg.name, dependencies);
  42. // Nothing to do if we weren't given any deps.
  43. if (!(dependencies && dependencies.length)) {
  44. log.verbose("npmInstallDependencies", "no dependencies to install");
  45. return Promise.resolve();
  46. }
  47. const packageJsonBkp = `${pkg.manifestLocation}.lerna_backup`;
  48. log.silly("npmInstallDependencies", "backup", pkg.manifestLocation);
  49. return fs.rename(pkg.manifestLocation, packageJsonBkp).then(() => {
  50. const cleanup = () => {
  51. log.silly("npmInstallDependencies", "cleanup", pkg.manifestLocation);
  52. // Need to do this one synchronously because we might be doing it on exit.
  53. fs.renameSync(packageJsonBkp, pkg.manifestLocation);
  54. };
  55. // If we die we need to be sure to put things back the way we found them.
  56. const unregister = onExit(cleanup);
  57. // We have a few housekeeping tasks to take care of whether we succeed or fail.
  58. const done = (finalError) => {
  59. cleanup();
  60. unregister();
  61. if (finalError) {
  62. throw finalError;
  63. }
  64. };
  65. // mutate a clone of the manifest with our new versions
  66. const tempJson = transformManifest(pkg, dependencies);
  67. log.silly("npmInstallDependencies", "writing tempJson", tempJson);
  68. // Write out our temporary cooked up package.json and then install.
  69. return writePkg(pkg.manifestLocation, tempJson)
  70. .then(() => npmInstall(pkg, config))
  71. .then(() => done(), done);
  72. });
  73. }
  74. function transformManifest(pkg, dependencies) {
  75. const json = pkg.toJSON();
  76. // a map of depName => depVersion (resolved by npm-package-arg)
  77. const depMap = new Map(
  78. dependencies.map((dep) => {
  79. const { name, rawSpec } = npa(dep, pkg.location);
  80. return [name, rawSpec || "*"];
  81. })
  82. );
  83. // don't run lifecycle scripts
  84. delete json.scripts;
  85. // filter all types of dependencies
  86. ["dependencies", "devDependencies", "optionalDependencies"].forEach((depType) => {
  87. const collection = json[depType];
  88. if (collection) {
  89. Object.keys(collection).forEach((depName) => {
  90. if (depMap.has(depName)) {
  91. // overwrite version to ensure it's always present (and accurate)
  92. collection[depName] = depMap.get(depName);
  93. // only add to one collection, also keeps track of leftovers
  94. depMap.delete(depName);
  95. } else {
  96. // filter out localDependencies and _duplicate_ external deps
  97. delete collection[depName];
  98. }
  99. });
  100. }
  101. });
  102. ["bundledDependencies", "bundleDependencies"].forEach((depType) => {
  103. const collection = json[depType];
  104. if (collection) {
  105. const newCollection = [];
  106. for (const depName of collection) {
  107. if (depMap.has(depName)) {
  108. newCollection.push(depName);
  109. depMap.delete(depName);
  110. }
  111. }
  112. json[depType] = newCollection;
  113. }
  114. });
  115. // add all leftovers (root hoisted)
  116. if (depMap.size) {
  117. if (!json.dependencies) {
  118. // TODO: this should definitely be versioned, not blown away after install :/
  119. json.dependencies = {};
  120. }
  121. depMap.forEach((depVersion, depName) => {
  122. json.dependencies[depName] = depVersion;
  123. });
  124. }
  125. return json;
  126. }