collect-updates.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. "use strict";
  2. const log = require("npmlog");
  3. const { describeRefSync } = require("@lerna/describe-ref");
  4. const { hasTags } = require("./lib/has-tags");
  5. const { collectPackages } = require("./lib/collect-packages");
  6. const { getPackagesForOption } = require("./lib/get-packages-for-option");
  7. const { makeDiffPredicate } = require("./lib/make-diff-predicate");
  8. module.exports.collectUpdates = collectUpdates;
  9. module.exports.collectPackages = collectPackages;
  10. module.exports.getPackagesForOption = getPackagesForOption;
  11. /**
  12. * @typedef {object} UpdateCollectorOptions
  13. * @property {string} [bump] The semver bump keyword (patch/minor/major) or explicit version used
  14. * @property {boolean} [canary] Whether or not to use a "nightly" range (`ref^..ref`) for commits
  15. * @property {string[]} [ignoreChanges]
  16. * A list of globs that match files/directories whose changes
  17. * should not be considered when identifying changed packages
  18. * @property {boolean} [includeMergedTags]
  19. * Whether or not to include the --first-parent flag when calling `git describe`
  20. * (awkwardly, pass `true` to _omit_ the flag, the default is to include it)
  21. * @property {boolean | string[]} [forcePublish] Which packages, if any, to always include
  22. * Force all packages to be versioned with `true`, or pass a list of globs that match package names
  23. * @property {string} [since] Ref to use when querying git, defaults to most recent annotated tag
  24. * @property {boolean} [conventionalCommits]
  25. * @property {boolean} [conventionalGraduate]
  26. * @property {boolean} [excludeDependents]
  27. */
  28. /**
  29. * Create a list of graph nodes representing packages changed since the previous release, tagged or otherwise.
  30. * @param {import("@lerna/package").Package[]} filteredPackages
  31. * @param {import("@lerna/package-graph").PackageGraph} packageGraph
  32. * @param {import("@lerna/child-process").ExecOpts} execOpts
  33. * @param {UpdateCollectorOptions} commandOptions
  34. */
  35. function collectUpdates(filteredPackages, packageGraph, execOpts, commandOptions) {
  36. const { forcePublish, conventionalCommits, conventionalGraduate, excludeDependents } = commandOptions;
  37. // If --conventional-commits and --conventional-graduate are both set, ignore --force-publish
  38. const useConventionalGraduate = conventionalCommits && conventionalGraduate;
  39. const forced = getPackagesForOption(useConventionalGraduate ? conventionalGraduate : forcePublish);
  40. const packages =
  41. filteredPackages.length === packageGraph.size
  42. ? packageGraph
  43. : new Map(filteredPackages.map(({ name }) => [name, packageGraph.get(name)]));
  44. let committish = commandOptions.since;
  45. if (hasTags(execOpts)) {
  46. // describe the last annotated tag in the current branch
  47. const { sha, refCount, lastTagName } = describeRefSync(execOpts, commandOptions.includeMergedTags);
  48. // TODO: warn about dirty tree?
  49. if (refCount === "0" && forced.size === 0 && !committish) {
  50. // no commits since previous release
  51. log.notice("", "Current HEAD is already released, skipping change detection.");
  52. return [];
  53. }
  54. if (commandOptions.canary) {
  55. // if it's a merge commit, it will return all the commits that were part of the merge
  56. // ex: If `ab7533e` had 2 commits, ab7533e^..ab7533e would contain 2 commits + the merge commit
  57. committish = `${sha}^..${sha}`;
  58. } else if (!committish) {
  59. // if no tags found, this will be undefined and we'll use the initial commit
  60. committish = lastTagName;
  61. }
  62. }
  63. if (forced.size) {
  64. // "warn" might seem a bit loud, but it is appropriate for logging anything _forced_
  65. log.warn(
  66. useConventionalGraduate ? "conventional-graduate" : "force-publish",
  67. forced.has("*") ? "all packages" : Array.from(forced.values()).join("\n")
  68. );
  69. }
  70. if (useConventionalGraduate) {
  71. // --conventional-commits --conventional-graduate
  72. if (forced.has("*")) {
  73. log.info("", "Graduating all prereleased packages");
  74. } else {
  75. log.info("", "Graduating prereleased packages");
  76. }
  77. } else if (!committish || forced.has("*")) {
  78. // --force-publish or no tag
  79. log.info("", "Assuming all packages changed");
  80. return collectPackages(packages, {
  81. onInclude: (name) => log.verbose("updated", name),
  82. excludeDependents,
  83. });
  84. }
  85. log.info("", `Looking for changed packages since ${committish}`);
  86. const hasDiff = makeDiffPredicate(committish, execOpts, commandOptions.ignoreChanges);
  87. const needsBump =
  88. !commandOptions.bump || commandOptions.bump.startsWith("pre")
  89. ? () => false
  90. : /* skip packages that have not been previously prereleased */
  91. (node) => node.prereleaseId;
  92. const isForced = (node, name) =>
  93. (forced.has("*") || forced.has(name)) && (useConventionalGraduate ? node.prereleaseId : true);
  94. return collectPackages(packages, {
  95. isCandidate: (node, name) => isForced(node, name) || needsBump(node) || hasDiff(node),
  96. onInclude: (name) => log.verbose("updated", name),
  97. excludeDependents,
  98. });
  99. }