@eldrforge/tree-execution 0.1.1 โ 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1507 -63
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -1,18 +1,114 @@
|
|
|
1
1
|
# @eldrforge/tree-execution
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A sophisticated parallel execution framework designed for orchestrating complex dependency-aware workflows in monorepo environments. Execute tasks across multiple packages with intelligent scheduling, automatic error recovery, and checkpoint/resume capabilities.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Table of Contents
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
7
|
+
- [Overview](#overview)
|
|
8
|
+
- [Key Features](#key-features)
|
|
9
|
+
- [Installation](#installation)
|
|
10
|
+
- [Core Concepts](#core-concepts)
|
|
11
|
+
- [Quick Start](#quick-start)
|
|
12
|
+
- [API Reference](#api-reference)
|
|
13
|
+
- [Advanced Usage](#advanced-usage)
|
|
14
|
+
- [Configuration](#configuration)
|
|
15
|
+
- [Error Handling & Recovery](#error-handling--recovery)
|
|
16
|
+
- [Real-World Examples](#real-world-examples)
|
|
17
|
+
- [Testing](#testing)
|
|
18
|
+
- [Architecture](#architecture)
|
|
19
|
+
- [Contributing](#contributing)
|
|
20
|
+
- [License](#license)
|
|
21
|
+
|
|
22
|
+
## Overview
|
|
23
|
+
|
|
24
|
+
`@eldrforge/tree-execution` provides a robust framework for executing tasks across interdependent packages in a monorepo. It handles:
|
|
25
|
+
|
|
26
|
+
- **Dependency-aware scheduling**: Automatically determines execution order based on package dependencies
|
|
27
|
+
- **Parallel execution**: Runs independent packages concurrently while respecting dependencies
|
|
28
|
+
- **Checkpoint/resume**: Save execution state and resume from where you left off
|
|
29
|
+
- **Error recovery**: Sophisticated retry logic with exponential backoff
|
|
30
|
+
- **Resource management**: CPU and memory-aware concurrency control
|
|
31
|
+
- **Progress tracking**: Real-time execution monitoring with detailed metrics
|
|
32
|
+
|
|
33
|
+
Originally developed as part of the kodrdriv toolkit, this library has been extracted for standalone use in any monorepo workflow.
|
|
34
|
+
|
|
35
|
+
## Key Features
|
|
36
|
+
|
|
37
|
+
### ๐ Intelligent Parallel Execution
|
|
38
|
+
Execute tasks across packages with automatic dependency resolution and optimal concurrency:
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
import { createTreeExecutor } from '@eldrforge/tree-execution';
|
|
42
|
+
import { buildDependencyGraph } from '@eldrforge/tree-core';
|
|
43
|
+
|
|
44
|
+
const graph = await buildDependencyGraph(['packages/*/package.json']);
|
|
45
|
+
const executor = createTreeExecutor();
|
|
46
|
+
await executor.execute({
|
|
47
|
+
tree: {
|
|
48
|
+
directories: ['packages'],
|
|
49
|
+
cmd: 'npm test',
|
|
50
|
+
parallel: true,
|
|
51
|
+
maxConcurrency: 4
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### ๐พ Checkpoint & Resume
|
|
57
|
+
Save execution state to resume long-running operations:
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
import { DynamicTaskPool } from '@eldrforge/tree-execution';
|
|
61
|
+
|
|
62
|
+
const pool = new DynamicTaskPool({
|
|
63
|
+
graph,
|
|
64
|
+
maxConcurrency: 4,
|
|
65
|
+
command: 'npm publish',
|
|
66
|
+
config: runConfig,
|
|
67
|
+
checkpointPath: './checkpoints/publish.json'
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// First run - saves checkpoints automatically
|
|
71
|
+
await pool.execute();
|
|
72
|
+
|
|
73
|
+
// Resume after interruption
|
|
74
|
+
await pool.execute({ continue: true });
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### ๐ก๏ธ Sophisticated Error Recovery
|
|
78
|
+
Automatic retry with exponential backoff and smart error classification:
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
const result = await executor.execute({
|
|
82
|
+
tree: {
|
|
83
|
+
cmd: 'npm test',
|
|
84
|
+
parallel: true,
|
|
85
|
+
retry: {
|
|
86
|
+
maxAttempts: 3,
|
|
87
|
+
initialDelayMs: 1000,
|
|
88
|
+
maxDelayMs: 10000,
|
|
89
|
+
backoffMultiplier: 2,
|
|
90
|
+
retriableErrors: ['ECONNRESET', 'ETIMEDOUT']
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### ๐ Real-time Progress Tracking
|
|
97
|
+
Monitor execution with detailed metrics:
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
import { createParallelProgressLogger } from '@eldrforge/tree-execution';
|
|
101
|
+
|
|
102
|
+
const logger = createParallelProgressLogger(totalPackages);
|
|
103
|
+
|
|
104
|
+
pool.on('package:started', ({ packageName }) => {
|
|
105
|
+
logger.onPackageStarted(packageName);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
pool.on('package:completed', ({ packageName, result }) => {
|
|
109
|
+
logger.onPackageCompleted(packageName, result);
|
|
110
|
+
});
|
|
111
|
+
```
|
|
16
112
|
|
|
17
113
|
## Installation
|
|
18
114
|
|
|
@@ -20,111 +116,1459 @@ Parallel execution framework and tree orchestration for monorepo workflows.
|
|
|
20
116
|
npm install @eldrforge/tree-execution
|
|
21
117
|
```
|
|
22
118
|
|
|
23
|
-
|
|
119
|
+
### Peer Dependencies
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
npm install @eldrforge/tree-core @eldrforge/git-tools @eldrforge/shared
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Core Concepts
|
|
126
|
+
|
|
127
|
+
### Dependency Graph
|
|
24
128
|
|
|
25
|
-
|
|
129
|
+
The foundation of execution is a dependency graph built from package.json files:
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
import { buildDependencyGraph } from '@eldrforge/tree-core';
|
|
133
|
+
|
|
134
|
+
// Scan for packages
|
|
135
|
+
const graph = await buildDependencyGraph([
|
|
136
|
+
'packages/*/package.json',
|
|
137
|
+
'apps/*/package.json'
|
|
138
|
+
]);
|
|
139
|
+
|
|
140
|
+
// Graph contains:
|
|
141
|
+
// - packages: Map<string, PackageInfo>
|
|
142
|
+
// - dependencies: Map<string, Set<string>>
|
|
143
|
+
// - dependents: Map<string, Set<string>>
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Execution State
|
|
147
|
+
|
|
148
|
+
The system tracks package states throughout execution:
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
interface ExecutionState {
|
|
152
|
+
pending: string[]; // Not yet started
|
|
153
|
+
ready: string[]; // Dependencies met, ready to run
|
|
154
|
+
running: RunningPackageSnapshot[]; // Currently executing
|
|
155
|
+
completed: string[]; // Successfully completed
|
|
156
|
+
failed: FailedPackageSnapshot[]; // Failed execution
|
|
157
|
+
skipped: string[]; // Skipped due to failed dependencies
|
|
158
|
+
skippedNoChanges: string[]; // Skipped (no code changes detected)
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### TreeExecutor vs DynamicTaskPool
|
|
163
|
+
|
|
164
|
+
**TreeExecutor** (Recommended):
|
|
165
|
+
- High-level, class-based API
|
|
166
|
+
- Dependency injection for custom commands
|
|
167
|
+
- State management and thread safety built-in
|
|
168
|
+
- Ideal for applications integrating tree execution
|
|
169
|
+
|
|
170
|
+
**DynamicTaskPool** (Advanced):
|
|
171
|
+
- Low-level execution engine
|
|
172
|
+
- Direct control over task scheduling
|
|
173
|
+
- Fine-grained event handling
|
|
174
|
+
- Ideal for custom execution frameworks
|
|
175
|
+
|
|
176
|
+
## Quick Start
|
|
177
|
+
|
|
178
|
+
### Basic Usage with TreeExecutor
|
|
26
179
|
|
|
27
180
|
```typescript
|
|
28
181
|
import { createTreeExecutor } from '@eldrforge/tree-execution';
|
|
29
182
|
|
|
30
|
-
// Create executor
|
|
183
|
+
// Create executor
|
|
31
184
|
const executor = createTreeExecutor({
|
|
32
185
|
commands: {
|
|
33
|
-
|
|
34
|
-
|
|
186
|
+
// Optional: inject custom commands
|
|
187
|
+
commit: myCommitHandler,
|
|
188
|
+
publish: myPublishHandler
|
|
35
189
|
}
|
|
36
190
|
});
|
|
37
191
|
|
|
38
|
-
// Execute
|
|
39
|
-
const result = await executor.execute(
|
|
192
|
+
// Execute a command across all packages
|
|
193
|
+
const result = await executor.execute({
|
|
194
|
+
tree: {
|
|
195
|
+
directories: ['packages'],
|
|
196
|
+
cmd: 'npm run build',
|
|
197
|
+
parallel: true,
|
|
198
|
+
maxConcurrency: 4
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
console.log(`Completed: ${result.completed.length}`);
|
|
203
|
+
console.log(`Failed: ${result.failed.length}`);
|
|
40
204
|
```
|
|
41
205
|
|
|
42
|
-
### Advanced
|
|
206
|
+
### Advanced Usage with DynamicTaskPool
|
|
43
207
|
|
|
44
208
|
```typescript
|
|
45
209
|
import { DynamicTaskPool } from '@eldrforge/tree-execution';
|
|
46
210
|
import { buildDependencyGraph } from '@eldrforge/tree-core';
|
|
47
211
|
|
|
48
212
|
// Build dependency graph
|
|
49
|
-
const graph = await buildDependencyGraph(
|
|
213
|
+
const graph = await buildDependencyGraph(['packages/*/package.json']);
|
|
50
214
|
|
|
51
|
-
// Create
|
|
215
|
+
// Create pool
|
|
52
216
|
const pool = new DynamicTaskPool({
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
217
|
+
graph,
|
|
218
|
+
maxConcurrency: 4,
|
|
219
|
+
command: 'npm test',
|
|
220
|
+
config: {
|
|
221
|
+
tree: { parallel: true }
|
|
222
|
+
},
|
|
223
|
+
checkpointPath: './checkpoints',
|
|
224
|
+
maxRetries: 3,
|
|
225
|
+
initialRetryDelay: 1000
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// Listen to events
|
|
229
|
+
pool.on('execution:started', ({ totalPackages }) => {
|
|
230
|
+
console.log(`Starting execution of ${totalPackages} packages`);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
pool.on('package:started', ({ packageName }) => {
|
|
234
|
+
console.log(`Started: ${packageName}`);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
pool.on('package:completed', ({ packageName, result }) => {
|
|
238
|
+
console.log(`Completed: ${packageName} in ${result.duration}ms`);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
pool.on('package:failed', ({ packageName, error, retriable }) => {
|
|
242
|
+
console.error(`Failed: ${packageName}`, error);
|
|
57
243
|
});
|
|
58
244
|
|
|
59
|
-
// Execute
|
|
245
|
+
// Execute
|
|
60
246
|
const result = await pool.execute();
|
|
61
247
|
|
|
62
|
-
|
|
63
|
-
|
|
248
|
+
// Check results
|
|
249
|
+
if (result.success) {
|
|
250
|
+
console.log('All packages completed successfully');
|
|
251
|
+
console.log(`Total time: ${result.metrics.totalDuration}ms`);
|
|
252
|
+
console.log(`Average concurrency: ${result.metrics.averageConcurrency}`);
|
|
253
|
+
} else {
|
|
254
|
+
console.error(`${result.failed.length} packages failed`);
|
|
255
|
+
result.failed.forEach(f => {
|
|
256
|
+
console.error(`- ${f.name}: ${f.error}`);
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## API Reference
|
|
262
|
+
|
|
263
|
+
### TreeExecutor
|
|
264
|
+
|
|
265
|
+
High-level orchestration class with dependency injection.
|
|
266
|
+
|
|
267
|
+
#### Constructor
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
constructor(options?: TreeExecutorOptions)
|
|
271
|
+
|
|
272
|
+
interface TreeExecutorOptions {
|
|
273
|
+
commands?: CommandRegistry; // Custom command handlers
|
|
274
|
+
logger?: Logger; // Custom logger instance
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
interface CommandRegistry {
|
|
278
|
+
updates?: CommandExecutor;
|
|
279
|
+
commit?: CommandExecutor;
|
|
280
|
+
link?: CommandExecutor;
|
|
281
|
+
unlink?: CommandExecutor;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
interface CommandExecutor {
|
|
285
|
+
execute(config: TreeExecutionConfig, mode?: string): Promise<any>;
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
#### Methods
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
// Execute tree command
|
|
293
|
+
async execute(config: TreeExecutionConfig): Promise<string>
|
|
294
|
+
|
|
295
|
+
// Get published versions (thread-safe)
|
|
296
|
+
async getPublishedVersions(): Promise<PublishedVersion[]>
|
|
297
|
+
|
|
298
|
+
// Add published version (thread-safe)
|
|
299
|
+
async addPublishedVersion(version: PublishedVersion): Promise<void>
|
|
300
|
+
|
|
301
|
+
// Get execution context (thread-safe)
|
|
302
|
+
async getExecutionContext(): Promise<TreeExecutionContext | null>
|
|
303
|
+
|
|
304
|
+
// Set execution context (thread-safe)
|
|
305
|
+
async setExecutionContext(context: TreeExecutionContext | null): Promise<void>
|
|
306
|
+
|
|
307
|
+
// Reset state (for testing)
|
|
308
|
+
async reset(): Promise<void>
|
|
309
|
+
|
|
310
|
+
// Get/set command executors
|
|
311
|
+
getCommand(name: keyof CommandRegistry): CommandExecutor | undefined
|
|
312
|
+
setCommand(name: keyof CommandRegistry, executor: CommandExecutor): void
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
#### Factory Function
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
import { createTreeExecutor } from '@eldrforge/tree-execution';
|
|
319
|
+
|
|
320
|
+
const executor = createTreeExecutor({
|
|
321
|
+
commands: {
|
|
322
|
+
commit: myCommitHandler
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### DynamicTaskPool
|
|
328
|
+
|
|
329
|
+
Low-level parallel execution engine.
|
|
330
|
+
|
|
331
|
+
#### Constructor
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
334
|
+
constructor(config: PoolConfig)
|
|
335
|
+
|
|
336
|
+
interface PoolConfig {
|
|
337
|
+
graph: DependencyGraph; // Dependency graph from @eldrforge/tree-core
|
|
338
|
+
maxConcurrency: number; // Maximum parallel tasks
|
|
339
|
+
command: string; // Command to execute
|
|
340
|
+
config: TreeExecutionConfig; // Execution configuration
|
|
341
|
+
checkpointPath?: string; // Path for checkpoint files
|
|
342
|
+
continue?: boolean; // Resume from checkpoint
|
|
343
|
+
maxRetries?: number; // Max retry attempts (default: 3)
|
|
344
|
+
initialRetryDelay?: number; // Initial retry delay ms (default: 1000)
|
|
345
|
+
maxRetryDelay?: number; // Max retry delay ms (default: 10000)
|
|
346
|
+
backoffMultiplier?: number; // Backoff multiplier (default: 2)
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
#### Methods
|
|
351
|
+
|
|
352
|
+
```typescript
|
|
353
|
+
// Execute all packages
|
|
354
|
+
async execute(): Promise<ExecutionResult>
|
|
355
|
+
|
|
356
|
+
// Abort execution
|
|
357
|
+
async abort(reason?: string): Promise<void>
|
|
358
|
+
|
|
359
|
+
// Get current checkpoint
|
|
360
|
+
async getCheckpoint(): Promise<ParallelExecutionCheckpoint>
|
|
361
|
+
|
|
362
|
+
// Load checkpoint and resume
|
|
363
|
+
private async loadCheckpoint(): Promise<void>
|
|
364
|
+
|
|
365
|
+
// Save checkpoint
|
|
366
|
+
private async saveCheckpoint(): Promise<void>
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
#### Events
|
|
370
|
+
|
|
371
|
+
```typescript
|
|
372
|
+
// Execution lifecycle
|
|
373
|
+
pool.on('execution:started', ({ totalPackages }) => { });
|
|
374
|
+
pool.on('execution:completed', (result: ExecutionResult) => { });
|
|
375
|
+
pool.on('execution:failed', (error: Error) => { });
|
|
376
|
+
pool.on('execution:aborted', ({ reason }) => { });
|
|
377
|
+
|
|
378
|
+
// Package lifecycle
|
|
379
|
+
pool.on('package:started', ({ packageName, attemptNumber }) => { });
|
|
380
|
+
pool.on('package:completed', ({ packageName, result }) => { });
|
|
381
|
+
pool.on('package:failed', ({ packageName, error, retriable, attemptNumber }) => { });
|
|
382
|
+
pool.on('package:retry', ({ packageName, attemptNumber, delayMs, error }) => { });
|
|
383
|
+
pool.on('package:skipped', ({ packageName, reason }) => { });
|
|
384
|
+
|
|
385
|
+
// Progress tracking
|
|
386
|
+
pool.on('progress:update', ({ completed, total, percentage }) => { });
|
|
387
|
+
pool.on('concurrency:changed', ({ active, available }) => { });
|
|
388
|
+
|
|
389
|
+
// Checkpointing
|
|
390
|
+
pool.on('checkpoint:saved', ({ path, packages }) => { });
|
|
391
|
+
pool.on('checkpoint:loaded', ({ path, resumePoint }) => { });
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
### Helper Functions
|
|
395
|
+
|
|
396
|
+
#### TreeExecutionAdapter
|
|
397
|
+
|
|
398
|
+
Bridges DynamicTaskPool with custom execution functions:
|
|
399
|
+
|
|
400
|
+
```typescript
|
|
401
|
+
import { TreeExecutionAdapter, ExecutePackageFunction } from '@eldrforge/tree-execution';
|
|
402
|
+
|
|
403
|
+
const executePackage: ExecutePackageFunction = async (
|
|
404
|
+
packageName,
|
|
405
|
+
packageInfo,
|
|
406
|
+
command,
|
|
407
|
+
config,
|
|
408
|
+
isDryRun,
|
|
409
|
+
index,
|
|
410
|
+
total,
|
|
411
|
+
allPackageNames,
|
|
412
|
+
isBuiltInCommand
|
|
413
|
+
) => {
|
|
414
|
+
// Custom execution logic
|
|
415
|
+
return { success: true };
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
const adapter = new TreeExecutionAdapter(poolConfig, executePackage);
|
|
419
|
+
const result = await adapter.execute();
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
#### Progress Logger
|
|
423
|
+
|
|
424
|
+
```typescript
|
|
425
|
+
import { createParallelProgressLogger } from '@eldrforge/tree-execution';
|
|
426
|
+
|
|
427
|
+
const logger = createParallelProgressLogger(totalPackages);
|
|
428
|
+
|
|
429
|
+
pool.on('package:started', ({ packageName }) => {
|
|
430
|
+
logger.onPackageStarted(packageName);
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
pool.on('package:completed', ({ packageName, result }) => {
|
|
434
|
+
logger.onPackageCompleted(packageName, result);
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
pool.on('package:failed', ({ packageName, error }) => {
|
|
438
|
+
logger.onPackageFailed(packageName, error);
|
|
439
|
+
});
|
|
64
440
|
```
|
|
65
441
|
|
|
66
|
-
|
|
442
|
+
#### Result Formatter
|
|
443
|
+
|
|
444
|
+
```typescript
|
|
445
|
+
import { formatParallelResult } from '@eldrforge/tree-execution';
|
|
446
|
+
|
|
447
|
+
const result = await pool.execute();
|
|
448
|
+
const formatted = formatParallelResult(result);
|
|
449
|
+
console.log(formatted); // Human-readable summary
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
### Component APIs
|
|
453
|
+
|
|
454
|
+
#### CheckpointManager
|
|
455
|
+
|
|
456
|
+
Manages execution state persistence:
|
|
457
|
+
|
|
458
|
+
```typescript
|
|
459
|
+
import { CheckpointManager } from '@eldrforge/tree-execution';
|
|
460
|
+
|
|
461
|
+
const manager = new CheckpointManager('./checkpoints');
|
|
462
|
+
|
|
463
|
+
// Save checkpoint
|
|
464
|
+
await manager.saveCheckpoint(executionState);
|
|
465
|
+
|
|
466
|
+
// Load latest checkpoint
|
|
467
|
+
const checkpoint = await manager.loadLatestCheckpoint();
|
|
468
|
+
|
|
469
|
+
// List all checkpoints
|
|
470
|
+
const checkpoints = await manager.listCheckpoints();
|
|
471
|
+
|
|
472
|
+
// Clean old checkpoints
|
|
473
|
+
await manager.cleanOldCheckpoints(maxAge);
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
#### RecoveryManager
|
|
477
|
+
|
|
478
|
+
Handles error recovery and state validation:
|
|
479
|
+
|
|
480
|
+
```typescript
|
|
481
|
+
import { RecoveryManager, loadRecoveryManager } from '@eldrforge/tree-execution';
|
|
482
|
+
|
|
483
|
+
// Load from checkpoint
|
|
484
|
+
const manager = await loadRecoveryManager('./checkpoint.json');
|
|
485
|
+
|
|
486
|
+
// Validate state
|
|
487
|
+
const validation = await manager.validateState();
|
|
488
|
+
if (!validation.isValid) {
|
|
489
|
+
console.error('Invalid state:', validation.errors);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Get recovery hints
|
|
493
|
+
const hints = manager.getRecoveryHints();
|
|
494
|
+
hints.forEach(hint => {
|
|
495
|
+
console.log(`[${hint.type}] ${hint.message}`);
|
|
496
|
+
if (hint.suggestedCommand) {
|
|
497
|
+
console.log(` Run: ${hint.suggestedCommand}`);
|
|
498
|
+
}
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
// Apply recovery options
|
|
502
|
+
await manager.applyRecoveryOptions({
|
|
503
|
+
skipPackages: ['pkg1'],
|
|
504
|
+
retryFailed: true
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
// Resume execution
|
|
508
|
+
const resumeConfig = await manager.getResumeConfig();
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
#### Scheduler
|
|
512
|
+
|
|
513
|
+
Determines execution order based on dependencies:
|
|
514
|
+
|
|
515
|
+
```typescript
|
|
516
|
+
import { Scheduler } from '@eldrforge/tree-execution';
|
|
517
|
+
|
|
518
|
+
const scheduler = new Scheduler(graph, dependencyChecker);
|
|
519
|
+
|
|
520
|
+
// Get next packages to execute
|
|
521
|
+
const next = scheduler.getNextPackages(
|
|
522
|
+
state,
|
|
523
|
+
resourceMonitor,
|
|
524
|
+
retryAttempts
|
|
525
|
+
);
|
|
526
|
+
|
|
527
|
+
// Check if package can run
|
|
528
|
+
const canRun = scheduler.canExecute(packageName, state);
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
#### ResourceMonitor
|
|
532
|
+
|
|
533
|
+
Tracks available execution slots:
|
|
67
534
|
|
|
68
535
|
```typescript
|
|
69
|
-
import {
|
|
536
|
+
import { ResourceMonitor } from '@eldrforge/tree-execution';
|
|
70
537
|
|
|
538
|
+
const monitor = new ResourceMonitor(maxConcurrency);
|
|
539
|
+
|
|
540
|
+
// Acquire slot
|
|
541
|
+
const success = monitor.acquire();
|
|
542
|
+
|
|
543
|
+
// Release slot
|
|
544
|
+
monitor.release();
|
|
545
|
+
|
|
546
|
+
// Check availability
|
|
547
|
+
if (monitor.isAvailable()) {
|
|
548
|
+
// Can start more tasks
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// Get metrics
|
|
552
|
+
const metrics = monitor.getMetrics();
|
|
553
|
+
console.log(`Active: ${metrics.activeCount}, Available: ${metrics.availableSlots}`);
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
#### DependencyChecker
|
|
557
|
+
|
|
558
|
+
Verifies package dependencies:
|
|
559
|
+
|
|
560
|
+
```typescript
|
|
561
|
+
import { DependencyChecker } from '@eldrforge/tree-execution';
|
|
562
|
+
|
|
563
|
+
const checker = new DependencyChecker(graph);
|
|
564
|
+
|
|
565
|
+
// Check if package is ready
|
|
566
|
+
const ready = checker.areAllDependenciesCompleted(packageName, state);
|
|
567
|
+
|
|
568
|
+
// Check if package can run (dependencies not failed)
|
|
569
|
+
const canRun = checker.canPackageRun(packageName, state);
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
#### CommandValidator
|
|
573
|
+
|
|
574
|
+
Validates commands for parallel execution:
|
|
575
|
+
|
|
576
|
+
```typescript
|
|
577
|
+
import { CommandValidator } from '@eldrforge/tree-execution';
|
|
578
|
+
|
|
579
|
+
const validator = new CommandValidator();
|
|
580
|
+
|
|
581
|
+
// Validate command
|
|
582
|
+
const result = validator.validate('npm test', config);
|
|
583
|
+
if (!result.isValid) {
|
|
584
|
+
console.error('Validation failed:', result.errors);
|
|
585
|
+
result.warnings.forEach(w => console.warn(w));
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// Check if command is safe for parallel execution
|
|
589
|
+
const isSafe = validator.isSafeForParallel('npm run build');
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
### Logger Integration
|
|
593
|
+
|
|
594
|
+
```typescript
|
|
595
|
+
import { setLogger, getLogger } from '@eldrforge/tree-execution';
|
|
596
|
+
|
|
597
|
+
// Set custom logger
|
|
71
598
|
setLogger({
|
|
72
|
-
info: (...args) =>
|
|
73
|
-
error: (...args) =>
|
|
74
|
-
warn: (...args) =>
|
|
75
|
-
verbose: (...args) =>
|
|
76
|
-
debug: (...args) =>
|
|
77
|
-
silly: (...args) =>
|
|
599
|
+
info: (...args) => console.log('[INFO]', ...args),
|
|
600
|
+
error: (...args) => console.error('[ERROR]', ...args),
|
|
601
|
+
warn: (...args) => console.warn('[WARN]', ...args),
|
|
602
|
+
verbose: (...args) => console.log('[VERBOSE]', ...args),
|
|
603
|
+
debug: (...args) => console.log('[DEBUG]', ...args),
|
|
604
|
+
silly: (...args) => console.log('[SILLY]', ...args)
|
|
78
605
|
});
|
|
606
|
+
|
|
607
|
+
// Get logger
|
|
608
|
+
const logger = getLogger();
|
|
609
|
+
logger.info('Execution started');
|
|
79
610
|
```
|
|
80
611
|
|
|
81
|
-
##
|
|
612
|
+
## Advanced Usage
|
|
82
613
|
|
|
83
|
-
###
|
|
84
|
-
High-level class-based API with dependency injection. Encapsulates all state management and provides clean integration points.
|
|
614
|
+
### Custom Command Integration
|
|
85
615
|
|
|
86
|
-
|
|
87
|
-
|
|
616
|
+
Integrate your own command handlers:
|
|
617
|
+
|
|
618
|
+
```typescript
|
|
619
|
+
import { createTreeExecutor, CommandExecutor } from '@eldrforge/tree-execution';
|
|
620
|
+
|
|
621
|
+
// Define custom command
|
|
622
|
+
class MyTestCommand implements CommandExecutor {
|
|
623
|
+
async execute(config: TreeExecutionConfig, mode?: string) {
|
|
624
|
+
// Custom test logic
|
|
625
|
+
console.log('Running tests with custom logic');
|
|
626
|
+
return { success: true };
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// Register command
|
|
631
|
+
const executor = createTreeExecutor({
|
|
632
|
+
commands: {
|
|
633
|
+
commit: new MyTestCommand()
|
|
634
|
+
}
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
// Execute
|
|
638
|
+
await executor.execute({
|
|
639
|
+
tree: {
|
|
640
|
+
directories: ['packages'],
|
|
641
|
+
builtInCommand: 'commit'
|
|
642
|
+
}
|
|
643
|
+
});
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
### Conditional Package Execution
|
|
647
|
+
|
|
648
|
+
Execute only packages matching certain criteria:
|
|
649
|
+
|
|
650
|
+
```typescript
|
|
651
|
+
import { buildDependencyGraph } from '@eldrforge/tree-core';
|
|
652
|
+
|
|
653
|
+
// Build graph with exclusions
|
|
654
|
+
const graph = await buildDependencyGraph(
|
|
655
|
+
['packages/*/package.json'],
|
|
656
|
+
['node_modules/**', '**/dist/**']
|
|
657
|
+
);
|
|
658
|
+
|
|
659
|
+
// Filter packages
|
|
660
|
+
const filteredGraph = {
|
|
661
|
+
...graph,
|
|
662
|
+
packages: new Map(
|
|
663
|
+
Array.from(graph.packages.entries())
|
|
664
|
+
.filter(([name, info]) => {
|
|
665
|
+
// Only include packages with tests
|
|
666
|
+
return info.scripts?.test !== undefined;
|
|
667
|
+
})
|
|
668
|
+
)
|
|
669
|
+
};
|
|
670
|
+
|
|
671
|
+
// Execute on filtered graph
|
|
672
|
+
const pool = new DynamicTaskPool({
|
|
673
|
+
graph: filteredGraph,
|
|
674
|
+
maxConcurrency: 4,
|
|
675
|
+
command: 'npm test',
|
|
676
|
+
config: {}
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
await pool.execute();
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
### Incremental Execution
|
|
683
|
+
|
|
684
|
+
Execute only packages with changes since last run:
|
|
685
|
+
|
|
686
|
+
```typescript
|
|
687
|
+
import { getGitStatusSummary } from '@eldrforge/git-tools';
|
|
688
|
+
import { findAllDependents } from '@eldrforge/tree-core';
|
|
689
|
+
|
|
690
|
+
// Get changed packages
|
|
691
|
+
const status = await getGitStatusSummary();
|
|
692
|
+
const changedFiles = [...status.staged, ...status.modified];
|
|
693
|
+
const changedPackages = new Set<string>();
|
|
694
|
+
|
|
695
|
+
changedFiles.forEach(file => {
|
|
696
|
+
const match = file.match(/packages\/([^\/]+)\//);
|
|
697
|
+
if (match) {
|
|
698
|
+
changedPackages.add(match[1]);
|
|
699
|
+
}
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
// Include all dependents of changed packages
|
|
703
|
+
const affectedPackages = new Set<string>();
|
|
704
|
+
changedPackages.forEach(pkg => {
|
|
705
|
+
affectedPackages.add(pkg);
|
|
706
|
+
const dependents = findAllDependents(graph, pkg);
|
|
707
|
+
dependents.forEach(dep => affectedPackages.add(dep));
|
|
708
|
+
});
|
|
88
709
|
|
|
89
|
-
|
|
90
|
-
|
|
710
|
+
// Execute only affected packages
|
|
711
|
+
const incrementalGraph = {
|
|
712
|
+
...graph,
|
|
713
|
+
packages: new Map(
|
|
714
|
+
Array.from(graph.packages.entries())
|
|
715
|
+
.filter(([name]) => affectedPackages.has(name))
|
|
716
|
+
)
|
|
717
|
+
};
|
|
91
718
|
|
|
92
|
-
|
|
93
|
-
|
|
719
|
+
const pool = new DynamicTaskPool({
|
|
720
|
+
graph: incrementalGraph,
|
|
721
|
+
maxConcurrency: 4,
|
|
722
|
+
command: 'npm run build',
|
|
723
|
+
config: {}
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
await pool.execute();
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
### Progress Dashboard
|
|
730
|
+
|
|
731
|
+
Build a real-time progress dashboard:
|
|
732
|
+
|
|
733
|
+
```typescript
|
|
734
|
+
import { DynamicTaskPool } from '@eldrforge/tree-execution';
|
|
735
|
+
|
|
736
|
+
const pool = new DynamicTaskPool(config);
|
|
737
|
+
|
|
738
|
+
// Track state
|
|
739
|
+
const state = {
|
|
740
|
+
total: 0,
|
|
741
|
+
completed: 0,
|
|
742
|
+
failed: 0,
|
|
743
|
+
running: new Set<string>()
|
|
744
|
+
};
|
|
745
|
+
|
|
746
|
+
pool.on('execution:started', ({ totalPackages }) => {
|
|
747
|
+
state.total = totalPackages;
|
|
748
|
+
updateDashboard();
|
|
749
|
+
});
|
|
750
|
+
|
|
751
|
+
pool.on('package:started', ({ packageName }) => {
|
|
752
|
+
state.running.add(packageName);
|
|
753
|
+
updateDashboard();
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
pool.on('package:completed', ({ packageName }) => {
|
|
757
|
+
state.running.delete(packageName);
|
|
758
|
+
state.completed++;
|
|
759
|
+
updateDashboard();
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
pool.on('package:failed', ({ packageName }) => {
|
|
763
|
+
state.running.delete(packageName);
|
|
764
|
+
state.failed++;
|
|
765
|
+
updateDashboard();
|
|
766
|
+
});
|
|
767
|
+
|
|
768
|
+
function updateDashboard() {
|
|
769
|
+
console.clear();
|
|
770
|
+
console.log('=== Execution Dashboard ===');
|
|
771
|
+
console.log(`Total: ${state.total}`);
|
|
772
|
+
console.log(`Completed: ${state.completed}`);
|
|
773
|
+
console.log(`Failed: ${state.failed}`);
|
|
774
|
+
console.log(`Running: ${state.running.size}`);
|
|
775
|
+
console.log(`Progress: ${((state.completed + state.failed) / state.total * 100).toFixed(1)}%`);
|
|
776
|
+
console.log('\nCurrently Running:');
|
|
777
|
+
state.running.forEach(pkg => console.log(` - ${pkg}`));
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
await pool.execute();
|
|
781
|
+
```
|
|
782
|
+
|
|
783
|
+
### Custom Retry Logic
|
|
784
|
+
|
|
785
|
+
Implement sophisticated retry strategies:
|
|
786
|
+
|
|
787
|
+
```typescript
|
|
788
|
+
const pool = new DynamicTaskPool({
|
|
789
|
+
graph,
|
|
790
|
+
maxConcurrency: 4,
|
|
791
|
+
command: 'npm test',
|
|
792
|
+
config: {
|
|
793
|
+
tree: {
|
|
794
|
+
retry: {
|
|
795
|
+
maxAttempts: 5,
|
|
796
|
+
initialDelayMs: 500,
|
|
797
|
+
maxDelayMs: 30000,
|
|
798
|
+
backoffMultiplier: 2.5,
|
|
799
|
+
retriableErrors: [
|
|
800
|
+
'ECONNRESET',
|
|
801
|
+
'ETIMEDOUT',
|
|
802
|
+
'ENOTFOUND',
|
|
803
|
+
'Test failed: flaky_test'
|
|
804
|
+
]
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
});
|
|
809
|
+
|
|
810
|
+
pool.on('package:retry', ({ packageName, attemptNumber, delayMs, error }) => {
|
|
811
|
+
console.log(`Retrying ${packageName} (attempt ${attemptNumber}) after ${delayMs}ms`);
|
|
812
|
+
console.log(`Reason: ${error.message}`);
|
|
813
|
+
});
|
|
814
|
+
|
|
815
|
+
await pool.execute();
|
|
816
|
+
```
|
|
817
|
+
|
|
818
|
+
### Recovery Workflow
|
|
819
|
+
|
|
820
|
+
Implement a complete recovery workflow:
|
|
821
|
+
|
|
822
|
+
```typescript
|
|
823
|
+
import { loadRecoveryManager } from '@eldrforge/tree-execution';
|
|
824
|
+
|
|
825
|
+
async function recoverExecution(checkpointPath: string) {
|
|
826
|
+
// Load recovery manager
|
|
827
|
+
const recovery = await loadRecoveryManager(checkpointPath);
|
|
828
|
+
|
|
829
|
+
// Validate state
|
|
830
|
+
const validation = await recovery.validateState();
|
|
831
|
+
if (!validation.isValid) {
|
|
832
|
+
console.error('State validation failed:');
|
|
833
|
+
validation.errors.forEach(err => console.error(` - ${err}`));
|
|
834
|
+
|
|
835
|
+
// Apply fixes
|
|
836
|
+
console.log('\nApplying recovery options...');
|
|
837
|
+
await recovery.applyRecoveryOptions({
|
|
838
|
+
skipPackages: validation.suggestedSkips || [],
|
|
839
|
+
retryFailed: true
|
|
840
|
+
});
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
// Show recovery hints
|
|
844
|
+
const hints = recovery.getRecoveryHints();
|
|
845
|
+
if (hints.length > 0) {
|
|
846
|
+
console.log('\nRecovery Hints:');
|
|
847
|
+
hints.forEach(hint => {
|
|
848
|
+
console.log(`[${hint.type.toUpperCase()}] ${hint.message}`);
|
|
849
|
+
if (hint.suggestedCommand) {
|
|
850
|
+
console.log(` Suggested: ${hint.suggestedCommand}`);
|
|
851
|
+
}
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
// Get resume configuration
|
|
856
|
+
const resumeConfig = await recovery.getResumeConfig();
|
|
857
|
+
|
|
858
|
+
// Resume execution
|
|
859
|
+
const pool = new DynamicTaskPool({
|
|
860
|
+
...resumeConfig,
|
|
861
|
+
continue: true
|
|
862
|
+
});
|
|
863
|
+
|
|
864
|
+
return await pool.execute();
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
// Use it
|
|
868
|
+
try {
|
|
869
|
+
const result = await recoverExecution('./checkpoints/publish.json');
|
|
870
|
+
console.log('Recovery successful!');
|
|
871
|
+
} catch (error) {
|
|
872
|
+
console.error('Recovery failed:', error);
|
|
873
|
+
}
|
|
874
|
+
```
|
|
875
|
+
|
|
876
|
+
## Configuration
|
|
877
|
+
|
|
878
|
+
### TreeExecutionConfig
|
|
879
|
+
|
|
880
|
+
Complete configuration interface:
|
|
881
|
+
|
|
882
|
+
```typescript
|
|
883
|
+
interface TreeExecutionConfig {
|
|
884
|
+
// Basic flags
|
|
885
|
+
dryRun?: boolean;
|
|
886
|
+
verbose?: boolean;
|
|
887
|
+
debug?: boolean;
|
|
888
|
+
|
|
889
|
+
// Tree-specific configuration
|
|
890
|
+
tree?: {
|
|
891
|
+
// Execution
|
|
892
|
+
directories?: string[]; // Directories to scan for packages
|
|
893
|
+
exclude?: string[]; // Patterns to exclude
|
|
894
|
+
cmd?: string; // Command to execute
|
|
895
|
+
builtInCommand?: string; // Built-in command name
|
|
896
|
+
packageArgument?: string; // Specific package to execute
|
|
897
|
+
|
|
898
|
+
// Parallel execution
|
|
899
|
+
parallel?: boolean; // Enable parallel execution
|
|
900
|
+
maxConcurrency?: number; // Max concurrent tasks (default: CPU cores)
|
|
901
|
+
|
|
902
|
+
// Retry configuration
|
|
903
|
+
retry?: {
|
|
904
|
+
maxAttempts?: number; // Max retry attempts (default: 3)
|
|
905
|
+
initialDelayMs?: number; // Initial delay (default: 1000)
|
|
906
|
+
maxDelayMs?: number; // Max delay (default: 10000)
|
|
907
|
+
backoffMultiplier?: number; // Backoff multiplier (default: 2)
|
|
908
|
+
retriableErrors?: string[]; // Retriable error patterns
|
|
909
|
+
};
|
|
910
|
+
|
|
911
|
+
// Recovery configuration
|
|
912
|
+
recovery?: {
|
|
913
|
+
checkpointInterval?: 'package' | 'batch'; // Checkpoint frequency
|
|
914
|
+
autoRetry?: boolean; // Auto-retry on failure
|
|
915
|
+
continueOnError?: boolean; // Continue on errors
|
|
916
|
+
};
|
|
917
|
+
|
|
918
|
+
// Monitoring configuration
|
|
919
|
+
monitoring?: {
|
|
920
|
+
showProgress?: boolean; // Show progress bar
|
|
921
|
+
showMetrics?: boolean; // Show metrics
|
|
922
|
+
logLevel?: 'minimal' | 'normal' | 'verbose'; // Log verbosity
|
|
923
|
+
};
|
|
924
|
+
|
|
925
|
+
// Recovery operations
|
|
926
|
+
continue?: boolean; // Resume from checkpoint
|
|
927
|
+
markCompleted?: string[]; // Mark packages as completed
|
|
928
|
+
skipPackages?: string[]; // Skip specific packages
|
|
929
|
+
retryFailed?: boolean; // Retry failed packages
|
|
930
|
+
skipFailed?: boolean; // Skip failed packages
|
|
931
|
+
resetPackage?: string; // Reset specific package state
|
|
932
|
+
|
|
933
|
+
// Advanced options
|
|
934
|
+
startFrom?: string; // Start from specific package
|
|
935
|
+
stopAt?: string; // Stop at specific package
|
|
936
|
+
status?: boolean; // Show execution status
|
|
937
|
+
validateState?: boolean; // Validate execution state
|
|
938
|
+
auditBranches?: boolean; // Audit git branches
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
```
|
|
942
|
+
|
|
943
|
+
### Environment Variables
|
|
944
|
+
|
|
945
|
+
Control execution through environment variables:
|
|
946
|
+
|
|
947
|
+
```bash
|
|
948
|
+
# Concurrency
|
|
949
|
+
TREE_MAX_CONCURRENCY=4
|
|
950
|
+
|
|
951
|
+
# Retry configuration
|
|
952
|
+
TREE_MAX_RETRIES=3
|
|
953
|
+
TREE_RETRY_DELAY=1000
|
|
954
|
+
TREE_RETRY_BACKOFF=2
|
|
955
|
+
|
|
956
|
+
# Checkpoint configuration
|
|
957
|
+
TREE_CHECKPOINT_PATH=./checkpoints
|
|
958
|
+
TREE_CHECKPOINT_INTERVAL=package
|
|
959
|
+
|
|
960
|
+
# Logging
|
|
961
|
+
TREE_LOG_LEVEL=verbose
|
|
962
|
+
TREE_SHOW_METRICS=true
|
|
963
|
+
```
|
|
964
|
+
|
|
965
|
+
## Error Handling & Recovery
|
|
966
|
+
|
|
967
|
+
### Error Classification
|
|
968
|
+
|
|
969
|
+
The system classifies errors as retriable or non-retriable:
|
|
970
|
+
|
|
971
|
+
```typescript
|
|
972
|
+
// Retriable errors (will be retried automatically)
|
|
973
|
+
const retriableErrors = [
|
|
974
|
+
'ECONNRESET', // Network connection reset
|
|
975
|
+
'ETIMEDOUT', // Network timeout
|
|
976
|
+
'ENOTFOUND', // DNS lookup failed
|
|
977
|
+
'ECONNREFUSED', // Connection refused
|
|
978
|
+
'Test.*flaky' // Flaky test patterns
|
|
979
|
+
];
|
|
980
|
+
|
|
981
|
+
// Non-retriable errors (fail immediately)
|
|
982
|
+
const nonRetriableErrors = [
|
|
983
|
+
'Syntax Error', // Code syntax errors
|
|
984
|
+
'Type Error', // Type errors
|
|
985
|
+
'Build failed', // Build failures
|
|
986
|
+
'Lint failed' // Linting failures
|
|
987
|
+
];
|
|
988
|
+
```
|
|
989
|
+
|
|
990
|
+
### Handling Failed Packages
|
|
991
|
+
|
|
992
|
+
```typescript
|
|
993
|
+
const result = await pool.execute();
|
|
994
|
+
|
|
995
|
+
if (!result.success) {
|
|
996
|
+
console.error('Execution failed');
|
|
997
|
+
|
|
998
|
+
// Analyze failures
|
|
999
|
+
result.failed.forEach(failure => {
|
|
1000
|
+
console.error(`\n${failure.name}:`);
|
|
1001
|
+
console.error(` Error: ${failure.error}`);
|
|
1002
|
+
console.error(` Retriable: ${failure.isRetriable}`);
|
|
1003
|
+
console.error(` Attempts: ${failure.attemptNumber}`);
|
|
1004
|
+
|
|
1005
|
+
if (failure.errorDetails) {
|
|
1006
|
+
console.error(` Type: ${failure.errorDetails.type}`);
|
|
1007
|
+
console.error(` Context: ${failure.errorDetails.context}`);
|
|
1008
|
+
if (failure.errorDetails.suggestion) {
|
|
1009
|
+
console.error(` Suggestion: ${failure.errorDetails.suggestion}`);
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
// Show affected packages
|
|
1014
|
+
console.error(` Dependents (skipped): ${failure.dependents.join(', ')}`);
|
|
1015
|
+
});
|
|
1016
|
+
|
|
1017
|
+
// Save checkpoint for recovery
|
|
1018
|
+
const checkpoint = await pool.getCheckpoint();
|
|
1019
|
+
await fs.writeFile('./failed-execution.json', JSON.stringify(checkpoint, null, 2));
|
|
1020
|
+
|
|
1021
|
+
console.log('\nCheckpoint saved to failed-execution.json');
|
|
1022
|
+
console.log('Resume with: --continue');
|
|
1023
|
+
}
|
|
1024
|
+
```
|
|
1025
|
+
|
|
1026
|
+
### Recovery Strategies
|
|
1027
|
+
|
|
1028
|
+
#### 1. Skip Failed Packages
|
|
1029
|
+
|
|
1030
|
+
```typescript
|
|
1031
|
+
const recovery = await loadRecoveryManager('./checkpoint.json');
|
|
1032
|
+
await recovery.applyRecoveryOptions({
|
|
1033
|
+
skipFailed: true
|
|
1034
|
+
});
|
|
1035
|
+
```
|
|
1036
|
+
|
|
1037
|
+
#### 2. Retry Failed Packages
|
|
1038
|
+
|
|
1039
|
+
```typescript
|
|
1040
|
+
await recovery.applyRecoveryOptions({
|
|
1041
|
+
retryFailed: true
|
|
1042
|
+
});
|
|
1043
|
+
```
|
|
1044
|
+
|
|
1045
|
+
#### 3. Skip Specific Packages
|
|
1046
|
+
|
|
1047
|
+
```typescript
|
|
1048
|
+
await recovery.applyRecoveryOptions({
|
|
1049
|
+
skipPackages: ['problematic-pkg1', 'problematic-pkg2']
|
|
1050
|
+
});
|
|
1051
|
+
```
|
|
1052
|
+
|
|
1053
|
+
#### 4. Mark Packages as Completed
|
|
1054
|
+
|
|
1055
|
+
```typescript
|
|
1056
|
+
await recovery.applyRecoveryOptions({
|
|
1057
|
+
markCompleted: ['manually-fixed-pkg']
|
|
1058
|
+
});
|
|
1059
|
+
```
|
|
1060
|
+
|
|
1061
|
+
#### 5. Reset Package State
|
|
1062
|
+
|
|
1063
|
+
```typescript
|
|
1064
|
+
await recovery.applyRecoveryOptions({
|
|
1065
|
+
resetPackage: 'pkg-to-reset'
|
|
1066
|
+
});
|
|
1067
|
+
```
|
|
1068
|
+
|
|
1069
|
+
## Real-World Examples
|
|
1070
|
+
|
|
1071
|
+
### Example 1: Monorepo Test Suite
|
|
1072
|
+
|
|
1073
|
+
Run tests across all packages with intelligent parallelization:
|
|
1074
|
+
|
|
1075
|
+
```typescript
|
|
1076
|
+
import { createTreeExecutor } from '@eldrforge/tree-execution';
|
|
1077
|
+
import { buildDependencyGraph } from '@eldrforge/tree-core';
|
|
1078
|
+
|
|
1079
|
+
async function runMonorepoTests() {
|
|
1080
|
+
// Build dependency graph
|
|
1081
|
+
const graph = await buildDependencyGraph(['packages/*/package.json']);
|
|
1082
|
+
|
|
1083
|
+
// Create executor
|
|
1084
|
+
const executor = createTreeExecutor();
|
|
1085
|
+
|
|
1086
|
+
// Run tests
|
|
1087
|
+
const result = await executor.execute({
|
|
1088
|
+
verbose: true,
|
|
1089
|
+
tree: {
|
|
1090
|
+
directories: ['packages'],
|
|
1091
|
+
cmd: 'npm test',
|
|
1092
|
+
parallel: true,
|
|
1093
|
+
maxConcurrency: 4,
|
|
1094
|
+
retry: {
|
|
1095
|
+
maxAttempts: 2,
|
|
1096
|
+
initialDelayMs: 1000,
|
|
1097
|
+
retriableErrors: ['Test.*flaky']
|
|
1098
|
+
},
|
|
1099
|
+
monitoring: {
|
|
1100
|
+
showProgress: true,
|
|
1101
|
+
showMetrics: true,
|
|
1102
|
+
logLevel: 'normal'
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
});
|
|
1106
|
+
|
|
1107
|
+
console.log(`\nTests completed: ${result.completed.length}/${result.totalPackages}`);
|
|
1108
|
+
return result.success ? 0 : 1;
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
runMonorepoTests().then(code => process.exit(code));
|
|
1112
|
+
```
|
|
1113
|
+
|
|
1114
|
+
### Example 2: Incremental Build System
|
|
1115
|
+
|
|
1116
|
+
Build only changed packages and their dependents:
|
|
1117
|
+
|
|
1118
|
+
```typescript
|
|
1119
|
+
import { DynamicTaskPool } from '@eldrforge/tree-execution';
|
|
1120
|
+
import { buildDependencyGraph, findAllDependents } from '@eldrforge/tree-core';
|
|
1121
|
+
import { getGitStatusSummary } from '@eldrforge/git-tools';
|
|
1122
|
+
|
|
1123
|
+
async function incrementalBuild() {
|
|
1124
|
+
// Get changed packages
|
|
1125
|
+
const graph = await buildDependencyGraph(['packages/*/package.json']);
|
|
1126
|
+
const status = await getGitStatusSummary();
|
|
1127
|
+
|
|
1128
|
+
const changedPackages = new Set<string>();
|
|
1129
|
+
[...status.staged, ...status.modified].forEach(file => {
|
|
1130
|
+
const match = file.match(/packages\/([^\/]+)\//);
|
|
1131
|
+
if (match) changedPackages.add(match[1]);
|
|
1132
|
+
});
|
|
1133
|
+
|
|
1134
|
+
// Find all affected packages (changed + dependents)
|
|
1135
|
+
const affectedPackages = new Set<string>();
|
|
1136
|
+
changedPackages.forEach(pkg => {
|
|
1137
|
+
affectedPackages.add(pkg);
|
|
1138
|
+
findAllDependents(graph, pkg).forEach(dep => affectedPackages.add(dep));
|
|
1139
|
+
});
|
|
1140
|
+
|
|
1141
|
+
console.log(`Changed packages: ${Array.from(changedPackages).join(', ')}`);
|
|
1142
|
+
console.log(`Total affected: ${affectedPackages.size}`);
|
|
1143
|
+
|
|
1144
|
+
if (affectedPackages.size === 0) {
|
|
1145
|
+
console.log('No packages to build');
|
|
1146
|
+
return;
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
// Build affected packages
|
|
1150
|
+
const filteredGraph = {
|
|
1151
|
+
...graph,
|
|
1152
|
+
packages: new Map(
|
|
1153
|
+
Array.from(graph.packages).filter(([name]) => affectedPackages.has(name))
|
|
1154
|
+
)
|
|
1155
|
+
};
|
|
1156
|
+
|
|
1157
|
+
const pool = new DynamicTaskPool({
|
|
1158
|
+
graph: filteredGraph,
|
|
1159
|
+
maxConcurrency: 4,
|
|
1160
|
+
command: 'npm run build',
|
|
1161
|
+
config: { tree: { parallel: true } }
|
|
1162
|
+
});
|
|
1163
|
+
|
|
1164
|
+
const result = await pool.execute();
|
|
1165
|
+
console.log(`\nBuilt ${result.completed.length} packages in ${result.metrics.totalDuration}ms`);
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
incrementalBuild().catch(console.error);
|
|
1169
|
+
```
|
|
1170
|
+
|
|
1171
|
+
### Example 3: Coordinated Package Publishing
|
|
1172
|
+
|
|
1173
|
+
Publish packages in dependency order with automatic version tracking:
|
|
1174
|
+
|
|
1175
|
+
```typescript
|
|
1176
|
+
import { DynamicTaskPool, createParallelProgressLogger } from '@eldrforge/tree-execution';
|
|
1177
|
+
import { buildDependencyGraph } from '@eldrforge/tree-core';
|
|
1178
|
+
|
|
1179
|
+
async function publishMonorepo() {
|
|
1180
|
+
const graph = await buildDependencyGraph(['packages/*/package.json']);
|
|
1181
|
+
|
|
1182
|
+
const pool = new DynamicTaskPool({
|
|
1183
|
+
graph,
|
|
1184
|
+
maxConcurrency: 2, // Limit publishing concurrency
|
|
1185
|
+
command: 'npm publish',
|
|
1186
|
+
config: {
|
|
1187
|
+
tree: {
|
|
1188
|
+
parallel: true,
|
|
1189
|
+
retry: {
|
|
1190
|
+
maxAttempts: 3,
|
|
1191
|
+
initialDelayMs: 2000,
|
|
1192
|
+
retriableErrors: ['ECONNRESET', 'ETIMEDOUT']
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
},
|
|
1196
|
+
checkpointPath: './checkpoints/publish.json'
|
|
1197
|
+
});
|
|
94
1198
|
|
|
95
|
-
|
|
96
|
-
|
|
1199
|
+
// Create progress logger
|
|
1200
|
+
const logger = createParallelProgressLogger(graph.packages.size);
|
|
97
1201
|
|
|
98
|
-
|
|
99
|
-
|
|
1202
|
+
// Track published versions
|
|
1203
|
+
const published: Array<{ name: string; version: string }> = [];
|
|
100
1204
|
|
|
101
|
-
|
|
102
|
-
|
|
1205
|
+
pool.on('package:started', ({ packageName }) => {
|
|
1206
|
+
logger.onPackageStarted(packageName);
|
|
1207
|
+
});
|
|
1208
|
+
|
|
1209
|
+
pool.on('package:completed', ({ packageName, result }) => {
|
|
1210
|
+
logger.onPackageCompleted(packageName, result);
|
|
1211
|
+
if (result.publishedVersion) {
|
|
1212
|
+
published.push({
|
|
1213
|
+
name: packageName,
|
|
1214
|
+
version: result.publishedVersion
|
|
1215
|
+
});
|
|
1216
|
+
}
|
|
1217
|
+
});
|
|
1218
|
+
|
|
1219
|
+
pool.on('package:failed', ({ packageName, error }) => {
|
|
1220
|
+
logger.onPackageFailed(packageName, error);
|
|
1221
|
+
});
|
|
1222
|
+
|
|
1223
|
+
const result = await pool.execute();
|
|
1224
|
+
|
|
1225
|
+
if (result.success) {
|
|
1226
|
+
console.log('\n=== Published Packages ===');
|
|
1227
|
+
published.forEach(p => console.log(`${p.name}@${p.version}`));
|
|
1228
|
+
} else {
|
|
1229
|
+
console.error('\n=== Publish Failed ===');
|
|
1230
|
+
console.error('Checkpoint saved for recovery');
|
|
1231
|
+
console.error('Resume with: --continue');
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
return result;
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
publishMonorepo().catch(console.error);
|
|
1238
|
+
```
|
|
103
1239
|
|
|
104
|
-
###
|
|
105
|
-
|
|
1240
|
+
### Example 4: Integration Test Suite
|
|
1241
|
+
|
|
1242
|
+
Run integration tests with environment setup/teardown:
|
|
1243
|
+
|
|
1244
|
+
```typescript
|
|
1245
|
+
import { DynamicTaskPool } from '@eldrforge/tree-execution';
|
|
1246
|
+
import { buildDependencyGraph } from '@eldrforge/tree-core';
|
|
1247
|
+
|
|
1248
|
+
async function runIntegrationTests() {
|
|
1249
|
+
const graph = await buildDependencyGraph(['services/*/package.json']);
|
|
1250
|
+
|
|
1251
|
+
// Setup: Start shared services
|
|
1252
|
+
console.log('Starting shared services...');
|
|
1253
|
+
await startDatabase();
|
|
1254
|
+
await startRedis();
|
|
1255
|
+
await startMessageQueue();
|
|
1256
|
+
|
|
1257
|
+
try {
|
|
1258
|
+
const pool = new DynamicTaskPool({
|
|
1259
|
+
graph,
|
|
1260
|
+
maxConcurrency: 2, // Limit to avoid resource contention
|
|
1261
|
+
command: 'npm run test:integration',
|
|
1262
|
+
config: {
|
|
1263
|
+
tree: {
|
|
1264
|
+
parallel: true,
|
|
1265
|
+
recovery: {
|
|
1266
|
+
continueOnError: false // Stop on first failure
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
});
|
|
1271
|
+
|
|
1272
|
+
pool.on('package:failed', async ({ packageName, error }) => {
|
|
1273
|
+
// Cleanup on failure
|
|
1274
|
+
console.error(`Test failed: ${packageName}`);
|
|
1275
|
+
await pool.abort('Test failure detected');
|
|
1276
|
+
});
|
|
1277
|
+
|
|
1278
|
+
const result = await pool.execute();
|
|
1279
|
+
return result;
|
|
1280
|
+
|
|
1281
|
+
} finally {
|
|
1282
|
+
// Teardown: Stop shared services
|
|
1283
|
+
console.log('Stopping shared services...');
|
|
1284
|
+
await stopMessageQueue();
|
|
1285
|
+
await stopRedis();
|
|
1286
|
+
await stopDatabase();
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
// Stub functions for services
|
|
1291
|
+
async function startDatabase() { /* ... */ }
|
|
1292
|
+
async function stopDatabase() { /* ... */ }
|
|
1293
|
+
async function startRedis() { /* ... */ }
|
|
1294
|
+
async function stopRedis() { /* ... */ }
|
|
1295
|
+
async function startMessageQueue() { /* ... */ }
|
|
1296
|
+
async function stopMessageQueue() { /* ... */ }
|
|
1297
|
+
|
|
1298
|
+
runIntegrationTests().catch(console.error);
|
|
1299
|
+
```
|
|
1300
|
+
|
|
1301
|
+
### Example 5: Custom Build Pipeline
|
|
1302
|
+
|
|
1303
|
+
Implement a multi-stage build pipeline:
|
|
1304
|
+
|
|
1305
|
+
```typescript
|
|
1306
|
+
import { DynamicTaskPool } from '@eldrforge/tree-execution';
|
|
1307
|
+
import { buildDependencyGraph } from '@eldrforge/tree-core';
|
|
1308
|
+
|
|
1309
|
+
async function buildPipeline() {
|
|
1310
|
+
const graph = await buildDependencyGraph(['packages/*/package.json']);
|
|
1311
|
+
|
|
1312
|
+
const stages = [
|
|
1313
|
+
{ name: 'Lint', command: 'npm run lint', concurrency: 8 },
|
|
1314
|
+
{ name: 'Type Check', command: 'npm run type-check', concurrency: 4 },
|
|
1315
|
+
{ name: 'Build', command: 'npm run build', concurrency: 4 },
|
|
1316
|
+
{ name: 'Test', command: 'npm test', concurrency: 4 }
|
|
1317
|
+
];
|
|
1318
|
+
|
|
1319
|
+
for (const stage of stages) {
|
|
1320
|
+
console.log(`\n=== Stage: ${stage.name} ===`);
|
|
1321
|
+
|
|
1322
|
+
const pool = new DynamicTaskPool({
|
|
1323
|
+
graph,
|
|
1324
|
+
maxConcurrency: stage.concurrency,
|
|
1325
|
+
command: stage.command,
|
|
1326
|
+
config: { tree: { parallel: true } }
|
|
1327
|
+
});
|
|
1328
|
+
|
|
1329
|
+
const result = await pool.execute();
|
|
1330
|
+
|
|
1331
|
+
if (!result.success) {
|
|
1332
|
+
console.error(`\nStage '${stage.name}' failed`);
|
|
1333
|
+
console.error(`Failed packages: ${result.failed.map(f => f.name).join(', ')}`);
|
|
1334
|
+
return false;
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
console.log(`Stage completed in ${result.metrics.totalDuration}ms`);
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
console.log('\n=== Pipeline Complete ===');
|
|
1341
|
+
return true;
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
buildPipeline().then(success => {
|
|
1345
|
+
process.exit(success ? 0 : 1);
|
|
1346
|
+
});
|
|
1347
|
+
```
|
|
106
1348
|
|
|
107
1349
|
## Testing
|
|
108
1350
|
|
|
109
|
-
This package includes
|
|
1351
|
+
This package includes comprehensive test coverage:
|
|
110
1352
|
|
|
111
1353
|
```bash
|
|
112
|
-
|
|
113
|
-
npm
|
|
1354
|
+
# Run all tests
|
|
1355
|
+
npm test
|
|
1356
|
+
|
|
1357
|
+
# Run with coverage
|
|
1358
|
+
npm run test:coverage
|
|
1359
|
+
|
|
1360
|
+
# Watch mode
|
|
1361
|
+
npm run test:watch
|
|
1362
|
+
```
|
|
1363
|
+
|
|
1364
|
+
### Test Structure
|
|
1365
|
+
|
|
1366
|
+
```
|
|
1367
|
+
tests/
|
|
1368
|
+
โโโ checkpoint/
|
|
1369
|
+
โ โโโ CheckpointManager.test.ts # Checkpoint persistence
|
|
1370
|
+
โโโ execution/
|
|
1371
|
+
โ โโโ CommandValidator.test.ts # Command validation
|
|
1372
|
+
โ โโโ DependencyChecker.test.ts # Dependency checking
|
|
1373
|
+
โ โโโ RecoveryManager.test.ts # Error recovery
|
|
1374
|
+
โ โโโ ResourceMonitor.test.ts # Resource tracking
|
|
1375
|
+
โ โโโ Scheduler.test.ts # Task scheduling
|
|
1376
|
+
โโโ integration/
|
|
1377
|
+
โ โโโ execution-flow.test.ts # End-to-end tests
|
|
1378
|
+
โโโ TreeExecutor.test.ts # TreeExecutor API
|
|
1379
|
+
โโโ util/
|
|
1380
|
+
โโโ logger.test.ts # Logging
|
|
1381
|
+
โโโ mutex.test.ts # Thread safety
|
|
1382
|
+
โโโ treeUtils.test.ts # Utilities
|
|
1383
|
+
```
|
|
1384
|
+
|
|
1385
|
+
### Coverage Report
|
|
1386
|
+
|
|
1387
|
+
- **TreeExecutor**: 94.82%
|
|
1388
|
+
- **Checkpoint Management**: 85%+
|
|
1389
|
+
- **Execution Framework**: 60%+
|
|
1390
|
+
- **Utilities**: 80%+
|
|
1391
|
+
|
|
1392
|
+
### Writing Tests
|
|
1393
|
+
|
|
1394
|
+
Example test for custom integration:
|
|
1395
|
+
|
|
1396
|
+
```typescript
|
|
1397
|
+
import { describe, it, expect } from 'vitest';
|
|
1398
|
+
import { createTreeExecutor } from '@eldrforge/tree-execution';
|
|
1399
|
+
import { buildDependencyGraph } from '@eldrforge/tree-core';
|
|
1400
|
+
|
|
1401
|
+
describe('Custom Integration', () => {
|
|
1402
|
+
it('should execute custom command', async () => {
|
|
1403
|
+
const executor = createTreeExecutor();
|
|
1404
|
+
|
|
1405
|
+
const result = await executor.execute({
|
|
1406
|
+
tree: {
|
|
1407
|
+
directories: ['test-packages'],
|
|
1408
|
+
cmd: 'echo "test"',
|
|
1409
|
+
parallel: false
|
|
1410
|
+
}
|
|
1411
|
+
});
|
|
1412
|
+
|
|
1413
|
+
expect(result).toBeDefined();
|
|
1414
|
+
});
|
|
1415
|
+
});
|
|
1416
|
+
```
|
|
1417
|
+
|
|
1418
|
+
## Architecture
|
|
1419
|
+
|
|
1420
|
+
### Component Overview
|
|
1421
|
+
|
|
1422
|
+
```
|
|
1423
|
+
@eldrforge/tree-execution
|
|
1424
|
+
โโโ TreeExecutor (High-level API)
|
|
1425
|
+
โ โโโ State management
|
|
1426
|
+
โ โโโ Command injection
|
|
1427
|
+
โ โโโ Thread safety
|
|
1428
|
+
โ
|
|
1429
|
+
โโโ DynamicTaskPool (Execution engine)
|
|
1430
|
+
โ โโโ Task scheduling
|
|
1431
|
+
โ โโโ Parallel coordination
|
|
1432
|
+
โ โโโ Event emission
|
|
1433
|
+
โ โโโ Checkpoint management
|
|
1434
|
+
โ
|
|
1435
|
+
โโโ Execution Components
|
|
1436
|
+
โ โโโ Scheduler (Task ordering)
|
|
1437
|
+
โ โโโ ResourceMonitor (Concurrency control)
|
|
1438
|
+
โ โโโ DependencyChecker (Dependency validation)
|
|
1439
|
+
โ โโโ CommandValidator (Command validation)
|
|
1440
|
+
โ
|
|
1441
|
+
โโโ Recovery Components
|
|
1442
|
+
โ โโโ CheckpointManager (State persistence)
|
|
1443
|
+
โ โโโ RecoveryManager (Error recovery)
|
|
1444
|
+
โ
|
|
1445
|
+
โโโ Utilities
|
|
1446
|
+
โโโ Logger (Logging abstraction)
|
|
1447
|
+
โโโ SimpleMutex (Thread safety)
|
|
1448
|
+
โโโ TreeUtils (Helper functions)
|
|
1449
|
+
```
|
|
1450
|
+
|
|
1451
|
+
### Execution Flow
|
|
1452
|
+
|
|
1453
|
+
```
|
|
1454
|
+
1. Build dependency graph
|
|
1455
|
+
โโ @eldrforge/tree-core
|
|
1456
|
+
|
|
1457
|
+
2. Initialize DynamicTaskPool
|
|
1458
|
+
โโ Create Scheduler
|
|
1459
|
+
โโ Create ResourceMonitor
|
|
1460
|
+
โโ Create DependencyChecker
|
|
1461
|
+
โโ Load checkpoint (if continuing)
|
|
1462
|
+
|
|
1463
|
+
3. Execution loop
|
|
1464
|
+
โโ Scheduler selects ready packages
|
|
1465
|
+
โโ ResourceMonitor allocates slots
|
|
1466
|
+
โโ Execute packages in parallel
|
|
1467
|
+
โโ Update state on completion/failure
|
|
1468
|
+
โโ Save checkpoints periodically
|
|
1469
|
+
โโ Emit progress events
|
|
1470
|
+
|
|
1471
|
+
4. Handle failures
|
|
1472
|
+
โโ Classify errors (retriable/non-retriable)
|
|
1473
|
+
โโ Retry with exponential backoff
|
|
1474
|
+
โโ Skip dependent packages on failure
|
|
1475
|
+
โโ Save recovery checkpoint
|
|
1476
|
+
|
|
1477
|
+
5. Complete execution
|
|
1478
|
+
โโ Calculate metrics
|
|
1479
|
+
โโ Generate summary
|
|
1480
|
+
โโ Return ExecutionResult
|
|
114
1481
|
```
|
|
115
1482
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
1483
|
+
### Thread Safety
|
|
1484
|
+
|
|
1485
|
+
All state mutations are protected by mutexes:
|
|
1486
|
+
|
|
1487
|
+
```typescript
|
|
1488
|
+
import { SimpleMutex } from '@eldrforge/tree-execution';
|
|
1489
|
+
|
|
1490
|
+
class StatefulComponent {
|
|
1491
|
+
private mutex = new SimpleMutex();
|
|
1492
|
+
private state: any = {};
|
|
1493
|
+
|
|
1494
|
+
async updateState(updates: any) {
|
|
1495
|
+
await this.mutex.runExclusive(async () => {
|
|
1496
|
+
this.state = { ...this.state, ...updates };
|
|
1497
|
+
});
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
```
|
|
120
1501
|
|
|
121
1502
|
## Dependencies
|
|
122
1503
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
-
|
|
1504
|
+
### Required Dependencies
|
|
1505
|
+
|
|
1506
|
+
- **@eldrforge/tree-core**: Dependency graph algorithms
|
|
1507
|
+
- **@eldrforge/git-tools**: Git operations
|
|
1508
|
+
- **@eldrforge/shared**: Shared utilities
|
|
1509
|
+
|
|
1510
|
+
### Peer Dependencies
|
|
1511
|
+
|
|
1512
|
+
These are automatically installed with the above packages:
|
|
1513
|
+
|
|
1514
|
+
- Node.js โฅ 18.0.0
|
|
1515
|
+
- TypeScript โฅ 5.0.0 (for development)
|
|
1516
|
+
|
|
1517
|
+
## Contributing
|
|
1518
|
+
|
|
1519
|
+
Contributions are welcome! Please follow these guidelines:
|
|
1520
|
+
|
|
1521
|
+
1. **Code Style**: Follow existing patterns and ESLint rules
|
|
1522
|
+
2. **Tests**: Add tests for new features
|
|
1523
|
+
3. **Documentation**: Update README and JSDoc comments
|
|
1524
|
+
4. **Commits**: Use conventional commit format
|
|
1525
|
+
|
|
1526
|
+
```bash
|
|
1527
|
+
# Setup development environment
|
|
1528
|
+
git clone https://github.com/calenvarek/tree-execution.git
|
|
1529
|
+
cd tree-execution
|
|
1530
|
+
npm install
|
|
1531
|
+
|
|
1532
|
+
# Run tests
|
|
1533
|
+
npm test
|
|
1534
|
+
|
|
1535
|
+
# Build
|
|
1536
|
+
npm run build
|
|
1537
|
+
|
|
1538
|
+
# Lint
|
|
1539
|
+
npm run lint
|
|
1540
|
+
```
|
|
1541
|
+
|
|
1542
|
+
### Development Scripts
|
|
1543
|
+
|
|
1544
|
+
```json
|
|
1545
|
+
{
|
|
1546
|
+
"build": "tsc",
|
|
1547
|
+
"test": "vitest run",
|
|
1548
|
+
"test:watch": "vitest",
|
|
1549
|
+
"test:coverage": "vitest run --coverage",
|
|
1550
|
+
"lint": "eslint 'src/**/*.ts'",
|
|
1551
|
+
"clean": "rm -rf dist coverage"
|
|
1552
|
+
}
|
|
1553
|
+
```
|
|
126
1554
|
|
|
127
1555
|
## License
|
|
128
1556
|
|
|
129
1557
|
MIT ยฉ Calen Varek
|
|
130
1558
|
|
|
1559
|
+
## Links
|
|
1560
|
+
|
|
1561
|
+
- **GitHub**: https://github.com/calenvarek/tree-execution
|
|
1562
|
+
- **Issues**: https://github.com/calenvarek/tree-execution/issues
|
|
1563
|
+
- **npm**: https://www.npmjs.com/package/@eldrforge/tree-execution
|
|
1564
|
+
|
|
1565
|
+
## Related Projects
|
|
1566
|
+
|
|
1567
|
+
- **@eldrforge/tree-core**: Dependency graph algorithms
|
|
1568
|
+
- **@eldrforge/git-tools**: Git operations toolkit
|
|
1569
|
+
- **@eldrforge/shared**: Shared utilities
|
|
1570
|
+
- **kodrdriv**: Complete monorepo toolkit (uses tree-execution)
|
|
1571
|
+
|
|
1572
|
+
---
|
|
1573
|
+
|
|
1574
|
+
Built with โค๏ธ for monorepo orchestration
|