query-graph.js 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. "use strict";
  2. const { PackageGraph } = require("@lerna/package-graph");
  3. /**
  4. * @typedef {object} QueryGraphConfig
  5. * @property {'allDependencies'|'dependencies'} [graphType] "dependencies" excludes devDependencies from graph
  6. * @property {boolean} [rejectCycles] Whether or not to reject dependency cycles
  7. */
  8. /**
  9. * A mutable PackageGraph used to query for next available packages.
  10. */
  11. class QueryGraph {
  12. /**
  13. * Sort a list of Packages topologically.
  14. *
  15. * @param {import("@lerna/package").Package[]} packages An array of Packages to build the list out of
  16. * @param {QueryGraphConfig} [options]
  17. *
  18. * @returns {import("@lerna/package").Package[]} A list of Package instances in topological order
  19. */
  20. static toposort(packages, options) {
  21. const graph = new QueryGraph(packages, options);
  22. const result = [];
  23. let batch = graph.getAvailablePackages();
  24. while (batch.length) {
  25. for (const node of batch) {
  26. // no need to take() in synchronous loop
  27. result.push(node.pkg);
  28. graph.markAsDone(node);
  29. }
  30. batch = graph.getAvailablePackages();
  31. }
  32. return result;
  33. }
  34. /**
  35. * @param {import("@lerna/package").Package[]} packages An array of Packages to build the graph out of
  36. * @param {QueryGraphConfig} [options]
  37. */
  38. constructor(packages, { graphType = "allDependencies", rejectCycles } = {}) {
  39. // Create dependency graph
  40. this.graph = new PackageGraph(packages, graphType);
  41. // Evaluate cycles
  42. this.cycles = this.graph.collapseCycles(rejectCycles);
  43. }
  44. _getNextLeaf() {
  45. return Array.from(this.graph.values()).filter((node) => node.localDependencies.size === 0);
  46. }
  47. _getNextCycle() {
  48. const cycle = Array.from(this.cycles).find((cycleNode) => cycleNode.localDependencies.size === 0);
  49. if (!cycle) {
  50. return [];
  51. }
  52. this.cycles.delete(cycle);
  53. return cycle.flatten();
  54. }
  55. getAvailablePackages() {
  56. // Get the next leaf nodes
  57. const availablePackages = this._getNextLeaf();
  58. if (availablePackages.length > 0) {
  59. return availablePackages;
  60. }
  61. return this._getNextCycle();
  62. }
  63. /**
  64. * @param {string} name
  65. */
  66. markAsTaken(name) {
  67. this.graph.delete(name);
  68. }
  69. /**
  70. * @param {import("@lerna/package-graph").PackageGraphNode} candidateNode
  71. */
  72. markAsDone(candidateNode) {
  73. this.graph.remove(candidateNode);
  74. for (const cycle of this.cycles) {
  75. cycle.unlink(candidateNode);
  76. }
  77. }
  78. }
  79. module.exports.QueryGraph = QueryGraph;
  80. module.exports.toposort = QueryGraph.toposort;