CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-lerna--run-topologically

Executes operations on packages in topological order with configurable concurrency and dependency graph support

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

index.mddocs/

@lerna/run-topologically

@lerna/run-topologically is an internal Lerna utility that executes operations on packages in maximally-saturated topological order. It orchestrates package processing by building a dependency graph and running tasks in parallel while respecting dependency relationships, using a queue-based approach with configurable concurrency.

Package Information

  • Package Name: @lerna/run-topologically
  • Package Type: npm
  • Language: JavaScript/TypeScript (CommonJS)
  • Installation: This is an internal Lerna tool, install via npm install lerna

Core Imports

const { runTopologically } = require("@lerna/run-topologically");

ES modules import (if supported by your environment):

import { runTopologically } from "@lerna/run-topologically";

Basic Usage

const { runTopologically } = require("@lerna/run-topologically");

// Execute a build operation on packages in dependency order
const packages = [/* array of Package instances */];

const buildResults = await runTopologically(
  packages,
  async (pkg) => {
    console.log(`Building ${pkg.name}...`);
    // Perform build operation
    return { name: pkg.name, success: true };
  },
  { concurrency: 4 }
);

console.log("All builds completed:", buildResults);

Architecture

The package is built around these key components:

  • Topological Execution: Processes packages in dependency order using graph traversal with maximally-saturated parallel execution
  • Concurrency Control: Uses p-queue to limit parallel execution while respecting dependencies (default: unlimited concurrency)
  • Dependency Graph: Leverages @lerna/query-graph for building and traversing package dependency relationships, with support for both allDependencies and dependencies graph types
  • Cycle Handling: Supports collapsing and processing dependency cycles when rejectCycles is false
  • Promise Aggregation: Collects and returns all results from runner executions in completion order
  • Recursive Queuing: Automatically queues next available packages as dependencies are satisfied

Capabilities

Topological Package Processing

Executes operations on packages in dependency order with controlled concurrency.

/**
 * Run callback in maximally-saturated topological order.
 * @template T
 * @param {import("@lerna/package").Package[]} packages - List of Package instances from @lerna/package
 * @param {(pkg: import("@lerna/package").Package) => Promise<T>} runner - Callback function (pkg: Package) => Promise<T>
 * @param {TopologicalConfig} [options] - Configuration options with destructuring defaults
 * @returns {Promise<T[]>} Array of values returned by runner callbacks
 */
function runTopologically(packages, runner, { concurrency, graphType, rejectCycles } = {});

Parameters:

  • packages: Array of Package instances from @lerna/package
  • runner: Async callback function that processes each package and returns a value
  • options: Optional configuration object

Options Configuration:

/**
 * Configuration object for topological execution
 * @typedef {import("@lerna/query-graph").QueryGraphConfig & { concurrency: number }} TopologicalConfig
 * @property {number} [concurrency] - Maximum number of concurrent operations (default: Infinity)
 * @property {'allDependencies'|'dependencies'} [graphType='allDependencies'] - Type of dependencies to consider
 * @property {boolean} [rejectCycles] - Whether to reject dependency cycles
 */

Usage Examples:

const { runTopologically } = require("@lerna/run-topologically");

// Basic usage with default options (unlimited concurrency)
const results = await runTopologically(
  packages,
  async (pkg) => {
    // Process package
    return pkg.name;
  }
);

// With concurrency control
const buildResults = await runTopologically(
  packages,
  async (pkg) => {
    console.log(`Building ${pkg.name}...`);
    // Simulate build process
    await new Promise(resolve => setTimeout(resolve, 1000));
    return { package: pkg.name, buildTime: Date.now() };
  },
  { concurrency: 2 }
);

// With specific dependency graph type and cycle rejection
const testResults = await runTopologically(
  packages,
  async (pkg) => {
    // Run tests only considering production dependencies
    return runTests(pkg);
  },
  { 
    graphType: 'dependencies', // Exclude devDependencies from graph
    concurrency: 4,
    rejectCycles: true // Reject if cycles detected
  }
);

// Example with error handling
try {
  const publishResults = await runTopologically(
    packages,
    async (pkg) => {
      if (pkg.private) {
        return { name: pkg.name, skipped: true };
      }
      // Perform publish operation
      return { name: pkg.name, published: true };
    },
    { concurrency: 1, rejectCycles: true }
  );
} catch (error) {
  console.error("Topological execution failed:", error.message);
}

Types

/**
 * Configuration object extending QueryGraphConfig with concurrency control
 * @typedef {import("@lerna/query-graph").QueryGraphConfig & { concurrency: number }} TopologicalConfig
 * @property {number} [concurrency] - Maximum number of concurrent operations
 * @property {'allDependencies'|'dependencies'} [graphType='allDependencies'] - Dependencies to consider
 * @property {boolean} [rejectCycles] - Whether to reject dependency cycles
 */

/**
 * Package instance from @lerna/package
 * @typedef {Object} Package
 * @property {string} name - Package name
 * @property {string} version - Package version
 * @property {string} location - Package location on filesystem
 * @property {string} manifestLocation - Path to package.json file
 * @property {string} nodeModulesLocation - Path to node_modules directory
 * @property {string} binLocation - Path to .bin directory
 * @property {string} contents - Path to package contents (may differ from location if publishConfig.directory is set)
 * @property {boolean} private - Whether package is marked as private
 * @property {Object} resolved - npm-package-arg resolved metadata
 * @property {string} rootPath - Root path of the monorepo
 * @property {Object} scripts - Package scripts from package.json
 * @property {Object} bin - Package bin entries
 * @property {Object} [dependencies] - Production dependencies
 * @property {Object} [devDependencies] - Development dependencies
 * @property {Object} [optionalDependencies] - Optional dependencies
 * @property {Object} [peerDependencies] - Peer dependencies
 * @property {function} get - Get arbitrary property from package.json
 * @property {function} set - Set arbitrary property in package.json
 * @property {function} toJSON - Get shallow copy of package.json
 * @property {function} refresh - Refresh package state from disk
 * @property {function} serialize - Write package.json changes to disk
 */

Implementation Details

Execution Flow:

  1. Creates a PQueue instance with specified concurrency limit
  2. Builds a QueryGraph from packages using the specified graph type
  3. Recursively processes available packages (those with satisfied dependencies):
    • Marks packages as "taken" to prevent duplicate processing
    • Queues runner execution for each available package
    • Marks packages as "done" upon completion
    • Automatically queues newly available packages
  4. Returns collected results when the queue becomes idle

Performance Characteristics:

  • Maximally-saturated execution: Runs as many packages in parallel as possible within dependency constraints
  • Memory efficient: Uses iterative queuing rather than pre-computing entire execution plan
  • Cycle aware: Handles dependency cycles gracefully when rejectCycles is false

Error Handling

The function will reject if:

  • The runner callback throws an error or returns a rejected promise
  • Dependency cycles are detected when rejectCycles: true is set
  • Invalid package dependency relationships are encountered
try {
  const results = await runTopologically(packages, runner, options);
} catch (error) {
  console.error("Topological execution failed:", error.message);
}

Common Error Scenarios:

  • Runner function failures propagate immediately, stopping execution
  • Cycle detection with rejectCycles: true prevents execution start
  • Invalid package objects or missing dependencies cause initialization errors

docs

index.md

tile.json