0
# @lerna/run-topologically
1
2
@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.
3
4
## Package Information
5
6
- **Package Name**: @lerna/run-topologically
7
- **Package Type**: npm
8
- **Language**: JavaScript/TypeScript (CommonJS)
9
- **Installation**: This is an internal Lerna tool, install via `npm install lerna`
10
11
## Core Imports
12
13
```javascript
14
const { runTopologically } = require("@lerna/run-topologically");
15
```
16
17
ES modules import (if supported by your environment):
18
19
```javascript
20
import { runTopologically } from "@lerna/run-topologically";
21
```
22
23
## Basic Usage
24
25
```javascript
26
const { runTopologically } = require("@lerna/run-topologically");
27
28
// Execute a build operation on packages in dependency order
29
const packages = [/* array of Package instances */];
30
31
const buildResults = await runTopologically(
32
packages,
33
async (pkg) => {
34
console.log(`Building ${pkg.name}...`);
35
// Perform build operation
36
return { name: pkg.name, success: true };
37
},
38
{ concurrency: 4 }
39
);
40
41
console.log("All builds completed:", buildResults);
42
```
43
44
## Architecture
45
46
The package is built around these key components:
47
48
- **Topological Execution**: Processes packages in dependency order using graph traversal with maximally-saturated parallel execution
49
- **Concurrency Control**: Uses `p-queue` to limit parallel execution while respecting dependencies (default: unlimited concurrency)
50
- **Dependency Graph**: Leverages `@lerna/query-graph` for building and traversing package dependency relationships, with support for both `allDependencies` and `dependencies` graph types
51
- **Cycle Handling**: Supports collapsing and processing dependency cycles when `rejectCycles` is false
52
- **Promise Aggregation**: Collects and returns all results from runner executions in completion order
53
- **Recursive Queuing**: Automatically queues next available packages as dependencies are satisfied
54
55
## Capabilities
56
57
### Topological Package Processing
58
59
Executes operations on packages in dependency order with controlled concurrency.
60
61
```javascript { .api }
62
/**
63
* Run callback in maximally-saturated topological order.
64
* @template T
65
* @param {import("@lerna/package").Package[]} packages - List of Package instances from @lerna/package
66
* @param {(pkg: import("@lerna/package").Package) => Promise<T>} runner - Callback function (pkg: Package) => Promise<T>
67
* @param {TopologicalConfig} [options] - Configuration options with destructuring defaults
68
* @returns {Promise<T[]>} Array of values returned by runner callbacks
69
*/
70
function runTopologically(packages, runner, { concurrency, graphType, rejectCycles } = {});
71
```
72
73
**Parameters:**
74
75
- `packages`: Array of `Package` instances from `@lerna/package`
76
- `runner`: Async callback function that processes each package and returns a value
77
- `options`: Optional configuration object
78
79
**Options Configuration:**
80
81
```javascript { .api }
82
/**
83
* Configuration object for topological execution
84
* @typedef {import("@lerna/query-graph").QueryGraphConfig & { concurrency: number }} TopologicalConfig
85
* @property {number} [concurrency] - Maximum number of concurrent operations (default: Infinity)
86
* @property {'allDependencies'|'dependencies'} [graphType='allDependencies'] - Type of dependencies to consider
87
* @property {boolean} [rejectCycles] - Whether to reject dependency cycles
88
*/
89
```
90
91
**Usage Examples:**
92
93
```javascript
94
const { runTopologically } = require("@lerna/run-topologically");
95
96
// Basic usage with default options (unlimited concurrency)
97
const results = await runTopologically(
98
packages,
99
async (pkg) => {
100
// Process package
101
return pkg.name;
102
}
103
);
104
105
// With concurrency control
106
const buildResults = await runTopologically(
107
packages,
108
async (pkg) => {
109
console.log(`Building ${pkg.name}...`);
110
// Simulate build process
111
await new Promise(resolve => setTimeout(resolve, 1000));
112
return { package: pkg.name, buildTime: Date.now() };
113
},
114
{ concurrency: 2 }
115
);
116
117
// With specific dependency graph type and cycle rejection
118
const testResults = await runTopologically(
119
packages,
120
async (pkg) => {
121
// Run tests only considering production dependencies
122
return runTests(pkg);
123
},
124
{
125
graphType: 'dependencies', // Exclude devDependencies from graph
126
concurrency: 4,
127
rejectCycles: true // Reject if cycles detected
128
}
129
);
130
131
// Example with error handling
132
try {
133
const publishResults = await runTopologically(
134
packages,
135
async (pkg) => {
136
if (pkg.private) {
137
return { name: pkg.name, skipped: true };
138
}
139
// Perform publish operation
140
return { name: pkg.name, published: true };
141
},
142
{ concurrency: 1, rejectCycles: true }
143
);
144
} catch (error) {
145
console.error("Topological execution failed:", error.message);
146
}
147
```
148
149
## Types
150
151
```javascript { .api }
152
/**
153
* Configuration object extending QueryGraphConfig with concurrency control
154
* @typedef {import("@lerna/query-graph").QueryGraphConfig & { concurrency: number }} TopologicalConfig
155
* @property {number} [concurrency] - Maximum number of concurrent operations
156
* @property {'allDependencies'|'dependencies'} [graphType='allDependencies'] - Dependencies to consider
157
* @property {boolean} [rejectCycles] - Whether to reject dependency cycles
158
*/
159
160
/**
161
* Package instance from @lerna/package
162
* @typedef {Object} Package
163
* @property {string} name - Package name
164
* @property {string} version - Package version
165
* @property {string} location - Package location on filesystem
166
* @property {string} manifestLocation - Path to package.json file
167
* @property {string} nodeModulesLocation - Path to node_modules directory
168
* @property {string} binLocation - Path to .bin directory
169
* @property {string} contents - Path to package contents (may differ from location if publishConfig.directory is set)
170
* @property {boolean} private - Whether package is marked as private
171
* @property {Object} resolved - npm-package-arg resolved metadata
172
* @property {string} rootPath - Root path of the monorepo
173
* @property {Object} scripts - Package scripts from package.json
174
* @property {Object} bin - Package bin entries
175
* @property {Object} [dependencies] - Production dependencies
176
* @property {Object} [devDependencies] - Development dependencies
177
* @property {Object} [optionalDependencies] - Optional dependencies
178
* @property {Object} [peerDependencies] - Peer dependencies
179
* @property {function} get - Get arbitrary property from package.json
180
* @property {function} set - Set arbitrary property in package.json
181
* @property {function} toJSON - Get shallow copy of package.json
182
* @property {function} refresh - Refresh package state from disk
183
* @property {function} serialize - Write package.json changes to disk
184
*/
185
```
186
187
## Implementation Details
188
189
**Execution Flow:**
190
1. Creates a `PQueue` instance with specified concurrency limit
191
2. Builds a `QueryGraph` from packages using the specified graph type
192
3. Recursively processes available packages (those with satisfied dependencies):
193
- Marks packages as "taken" to prevent duplicate processing
194
- Queues runner execution for each available package
195
- Marks packages as "done" upon completion
196
- Automatically queues newly available packages
197
4. Returns collected results when the queue becomes idle
198
199
**Performance Characteristics:**
200
- **Maximally-saturated execution**: Runs as many packages in parallel as possible within dependency constraints
201
- **Memory efficient**: Uses iterative queuing rather than pre-computing entire execution plan
202
- **Cycle aware**: Handles dependency cycles gracefully when `rejectCycles` is false
203
204
## Error Handling
205
206
The function will reject if:
207
208
- The runner callback throws an error or returns a rejected promise
209
- Dependency cycles are detected when `rejectCycles: true` is set
210
- Invalid package dependency relationships are encountered
211
212
```javascript
213
try {
214
const results = await runTopologically(packages, runner, options);
215
} catch (error) {
216
console.error("Topological execution failed:", error.message);
217
}
218
```
219
220
**Common Error Scenarios:**
221
- Runner function failures propagate immediately, stopping execution
222
- Cycle detection with `rejectCycles: true` prevents execution start
223
- Invalid package objects or missing dependencies cause initialization errors