Executes operations on packages in topological order with configurable concurrency and dependency graph support
npx @tessl/cli install tessl/npm-lerna--run-topologically@6.4.0@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.
npm install lernaconst { runTopologically } = require("@lerna/run-topologically");ES modules import (if supported by your environment):
import { runTopologically } from "@lerna/run-topologically";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);The package is built around these key components:
p-queue to limit parallel execution while respecting dependencies (default: unlimited concurrency)@lerna/query-graph for building and traversing package dependency relationships, with support for both allDependencies and dependencies graph typesrejectCycles is falseExecutes 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/packagerunner: Async callback function that processes each package and returns a valueoptions: Optional configuration objectOptions 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);
}/**
* 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
*/Execution Flow:
PQueue instance with specified concurrency limitQueryGraph from packages using the specified graph typePerformance Characteristics:
rejectCycles is falseThe function will reject if:
rejectCycles: true is settry {
const results = await runTopologically(packages, runner, options);
} catch (error) {
console.error("Topological execution failed:", error.message);
}Common Error Scenarios:
rejectCycles: true prevents execution start