npm-publish.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. "use strict";
  2. const fs = require("fs-extra");
  3. const path = require("path");
  4. const log = require("npmlog");
  5. const { publish } = require("libnpmpublish");
  6. const pify = require("pify");
  7. const readJSON = require("read-package-json");
  8. const { runLifecycle } = require("@lerna/run-lifecycle");
  9. const npa = require("npm-package-arg");
  10. const { otplease } = require("@lerna/otplease");
  11. module.exports.npmPublish = npmPublish;
  12. const readJSONAsync = pify(readJSON);
  13. /**
  14. * @typedef {object} NpmPublishOptions
  15. * @property {boolean} [dryRun]
  16. * @property {string} [tag] Passed to libnpmpublish as `opts.defaultTag` to preserve npm v6 back-compat
  17. */
  18. /**
  19. * Alias dash-cased npmConf to camelCase
  20. * @param {NpmPublishOptions} obj
  21. * @returns {NpmPublishOptions}
  22. */
  23. function flattenOptions(obj) {
  24. return {
  25. // eslint-disable-next-line dot-notation -- (npm v7 compat)
  26. defaultTag: obj["tag"] || "latest",
  27. dryRun: obj["dry-run"],
  28. ...obj,
  29. };
  30. }
  31. /**
  32. * @typedef {import('npm-registry-fetch').FetchOptions & { access?: 'public' | 'restricted'; defaultTag?: string; }} LibNpmPublishOptions https://github.com/npm/libnpmpublish#opts
  33. */
  34. /**
  35. * Publish a package to the configured registry.
  36. * @param {import("@lerna/package").Package} pkg
  37. * @param {string} tarFilePath
  38. * @param {LibNpmPublishOptions & NpmPublishOptions} [options]
  39. * @param {import("@lerna/otplease").OneTimePasswordCache} [otpCache]
  40. */
  41. function npmPublish(pkg, tarFilePath, options = {}, otpCache) {
  42. const { dryRun, ...remainingOptions } = flattenOptions(options);
  43. const { scope } = npa(pkg.name);
  44. // pass only the package scope to libnpmpublish
  45. const opts = {
  46. log,
  47. ...remainingOptions,
  48. projectScope: scope,
  49. };
  50. opts.log.verbose("publish", pkg.name);
  51. let chain = Promise.resolve();
  52. if (!dryRun) {
  53. chain = chain.then(() => {
  54. let { manifestLocation } = pkg;
  55. if (pkg.contents !== pkg.location) {
  56. // "rebase" manifest used to generated directory
  57. manifestLocation = path.join(pkg.contents, "package.json");
  58. }
  59. return Promise.all([fs.readFile(tarFilePath), readJSONAsync(manifestLocation)]);
  60. });
  61. chain = chain.then(([tarData, manifest]) => {
  62. // non-default tag needs to override publishConfig.tag,
  63. // which is merged into opts below if necessary
  64. if (
  65. opts.defaultTag !== "latest" &&
  66. manifest.publishConfig &&
  67. manifest.publishConfig.tag &&
  68. manifest.publishConfig.tag !== opts.defaultTag
  69. ) {
  70. // eslint-disable-next-line no-param-reassign
  71. manifest.publishConfig.tag = opts.defaultTag;
  72. }
  73. // publishConfig is no longer consumed in n-r-f, so merge here
  74. if (manifest.publishConfig) {
  75. Object.assign(opts, publishConfigToOpts(manifest.publishConfig));
  76. }
  77. return otplease((innerOpts) => publish(manifest, tarData, innerOpts), opts, otpCache).catch((err) => {
  78. opts.log.silly("", err);
  79. opts.log.error(err.code, (err.body && err.body.error) || err.message);
  80. // avoid dumping logs, this isn't a lerna problem
  81. err.name = "ValidationError";
  82. // ensure process exits non-zero
  83. process.exitCode = "errno" in err ? err.errno : 1;
  84. // re-throw to break chain upstream
  85. throw err;
  86. });
  87. });
  88. }
  89. chain = chain.then(() => runLifecycle(pkg, "publish", opts));
  90. chain = chain.then(() => runLifecycle(pkg, "postpublish", opts));
  91. return chain;
  92. }
  93. /**
  94. * @typedef {object} PackagePublishConfig
  95. * @property {'public' | 'restricted'} [access]
  96. * @property {string} [registry]
  97. * @property {string} [tag]
  98. */
  99. /**
  100. * Obtain an object suitable for assignment onto existing options from `pkg.publishConfig`.
  101. * @param {PackagePublishConfig} publishConfig
  102. * @returns {Omit<PackagePublishConfig, 'tag'> & { defaultTag?: string }}
  103. */
  104. function publishConfigToOpts(publishConfig) {
  105. const opts = { ...publishConfig };
  106. // npm v7 renamed tag internally
  107. if (publishConfig.tag) {
  108. opts.defaultTag = publishConfig.tag;
  109. delete opts.tag;
  110. }
  111. return opts;
  112. }