run-lifecycle.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. "use strict";
  2. const log = require("npmlog");
  3. const runScript = require("npm-lifecycle");
  4. const npmConf = require("@lerna/npm-conf");
  5. module.exports.runLifecycle = runLifecycle;
  6. module.exports.createRunner = createRunner;
  7. /**
  8. * @typedef {object} LifecycleConfig
  9. * @property {typeof log} [log]
  10. * @property {boolean} [ignorePrepublish]
  11. * @property {boolean} [ignoreScripts]
  12. * @property {string} [nodeOptions]
  13. * @property {string} [scriptShell]
  14. * @property {boolean} [scriptsPrependNodePath]
  15. * @property {boolean} [unsafePerm=true]
  16. */
  17. /**
  18. * Alias dash-cased npmConf to camelCase
  19. * @param {LifecycleConfig} obj
  20. * @returns {LifecycleConfig}
  21. */
  22. function flattenOptions(obj) {
  23. return {
  24. ignorePrepublish: obj["ignore-prepublish"],
  25. ignoreScripts: obj["ignore-scripts"],
  26. nodeOptions: obj["node-options"],
  27. scriptShell: obj["script-shell"],
  28. scriptsPrependNodePath: obj["scripts-prepend-node-path"],
  29. unsafePerm: obj["unsafe-perm"],
  30. ...obj,
  31. };
  32. }
  33. /**
  34. * Run a lifecycle script for a package.
  35. * @param {import("@lerna/package").Package} pkg
  36. * @param {string} stage
  37. * @param {LifecycleConfig} options
  38. */
  39. function runLifecycle(pkg, stage, options) {
  40. // back-compat for @lerna/npm-conf instances
  41. // https://github.com/isaacs/proto-list/blob/27764cd/proto-list.js#L14
  42. if ("root" in options) {
  43. // eslint-disable-next-line no-param-reassign
  44. options = options.snapshot;
  45. }
  46. const opts = {
  47. log,
  48. unsafePerm: true,
  49. ...flattenOptions(options),
  50. };
  51. const dir = pkg.location;
  52. const config = {};
  53. if (opts.ignoreScripts) {
  54. opts.log.verbose("lifecycle", "%j ignored in %j", stage, pkg.name);
  55. return Promise.resolve();
  56. }
  57. if (!pkg.scripts || !pkg.scripts[stage]) {
  58. opts.log.silly("lifecycle", "No script for %j in %j, continuing", stage, pkg.name);
  59. return Promise.resolve();
  60. }
  61. if (stage === "prepublish" && opts.ignorePrepublish) {
  62. opts.log.verbose("lifecycle", "%j ignored in %j", stage, pkg.name);
  63. return Promise.resolve();
  64. }
  65. // https://github.com/zkat/figgy-pudding/blob/7d68bd3/index.js#L42-L64
  66. for (const [key, val] of Object.entries(opts)) {
  67. // omit falsy values and circular objects
  68. if (val != null && key !== "log" && key !== "logstream") {
  69. config[key] = val;
  70. }
  71. }
  72. /* istanbul ignore else */
  73. // eslint-disable-next-line no-underscore-dangle
  74. if (pkg.__isLernaPackage) {
  75. // To ensure npm-lifecycle creates the correct npm_package_* env vars,
  76. // we must pass the _actual_ JSON instead of our fancy Package thingy
  77. // eslint-disable-next-line no-param-reassign
  78. pkg = pkg.toJSON();
  79. }
  80. // TODO: remove pkg._id when npm-lifecycle no longer relies on it
  81. pkg._id = `${pkg.name}@${pkg.version}`; // eslint-disable-line
  82. opts.log.silly("lifecycle", "%j starting in %j", stage, pkg.name);
  83. return runScript(pkg, stage, dir, {
  84. config,
  85. dir,
  86. failOk: false,
  87. log: opts.log,
  88. // bring along camelCased aliases
  89. nodeOptions: opts.nodeOptions,
  90. scriptShell: opts.scriptShell,
  91. scriptsPrependNodePath: opts.scriptsPrependNodePath,
  92. unsafePerm: opts.unsafePerm,
  93. }).then(
  94. () => {
  95. opts.log.silly("lifecycle", "%j finished in %j", stage, pkg.name);
  96. },
  97. (err) => {
  98. // propagate the exit code
  99. const exitCode = err.errno || 1;
  100. // error logging has already occurred on stderr, but we need to stop the chain
  101. log.error("lifecycle", "%j errored in %j, exiting %d", stage, pkg.name, exitCode);
  102. // ensure clean logging, avoiding spurious log dump
  103. err.name = "ValidationError";
  104. // our yargs.fail() handler expects a numeric .exitCode, not .errno
  105. err.exitCode = exitCode;
  106. process.exitCode = exitCode;
  107. // stop the chain
  108. throw err;
  109. }
  110. );
  111. }
  112. function createRunner(commandOptions) {
  113. const cfg = npmConf(commandOptions).snapshot;
  114. return (pkg, stage) => runLifecycle(pkg, stage, cfg);
  115. }