@calmo/task-runner 1.2.3 → 2.0.0
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/.jules/sentinel.md +4 -0
- package/AGENTS.md +4 -1
- package/CHANGELOG.md +20 -15
- package/GEMINI.md +2 -0
- package/coverage/coverage-final.json +2 -2
- package/coverage/index.html +9 -9
- package/coverage/lcov-report/index.html +9 -9
- package/coverage/lcov-report/src/EventBus.ts.html +1 -1
- package/coverage/lcov-report/src/TaskGraphValidator.ts.html +134 -53
- package/coverage/lcov-report/src/TaskRunner.ts.html +1 -1
- package/coverage/lcov-report/src/WorkflowExecutor.ts.html +66 -60
- package/coverage/lcov-report/src/contracts/RunnerEvents.ts.html +1 -1
- package/coverage/lcov-report/src/contracts/index.html +1 -1
- package/coverage/lcov-report/src/index.html +11 -11
- package/coverage/lcov.info +129 -107
- package/coverage/src/EventBus.ts.html +1 -1
- package/coverage/src/TaskGraphValidator.ts.html +134 -53
- package/coverage/src/TaskRunner.ts.html +1 -1
- package/coverage/src/WorkflowExecutor.ts.html +66 -60
- package/coverage/src/contracts/RunnerEvents.ts.html +1 -1
- package/coverage/src/contracts/index.html +1 -1
- package/coverage/src/index.html +11 -11
- package/dist/TaskGraphValidator.js +39 -16
- package/dist/TaskGraphValidator.js.map +1 -1
- package/dist/WorkflowExecutor.d.ts +8 -1
- package/dist/WorkflowExecutor.js +29 -36
- package/dist/WorkflowExecutor.js.map +1 -1
- package/package.json +1 -1
- package/src/TaskGraphValidator.ts +46 -19
- package/src/WorkflowExecutor.ts +37 -35
- package/test-report.xml +47 -43
package/coverage/src/index.html
CHANGED
|
@@ -25,28 +25,28 @@
|
|
|
25
25
|
<div class='fl pad1y space-right2'>
|
|
26
26
|
<span class="strong">100% </span>
|
|
27
27
|
<span class="quiet">Statements</span>
|
|
28
|
-
<span class='fraction'>
|
|
28
|
+
<span class='fraction'>126/126</span>
|
|
29
29
|
</div>
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
<div class='fl pad1y space-right2'>
|
|
33
33
|
<span class="strong">100% </span>
|
|
34
34
|
<span class="quiet">Branches</span>
|
|
35
|
-
<span class='fraction'>
|
|
35
|
+
<span class='fraction'>48/48</span>
|
|
36
36
|
</div>
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
<div class='fl pad1y space-right2'>
|
|
40
40
|
<span class="strong">100% </span>
|
|
41
41
|
<span class="quiet">Functions</span>
|
|
42
|
-
<span class='fraction'>
|
|
42
|
+
<span class='fraction'>25/25</span>
|
|
43
43
|
</div>
|
|
44
44
|
|
|
45
45
|
|
|
46
46
|
<div class='fl pad1y space-right2'>
|
|
47
47
|
<span class="strong">100% </span>
|
|
48
48
|
<span class="quiet">Lines</span>
|
|
49
|
-
<span class='fraction'>
|
|
49
|
+
<span class='fraction'>123/123</span>
|
|
50
50
|
</div>
|
|
51
51
|
|
|
52
52
|
|
|
@@ -99,13 +99,13 @@
|
|
|
99
99
|
<div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div>
|
|
100
100
|
</td>
|
|
101
101
|
<td data-value="100" class="pct high">100%</td>
|
|
102
|
-
<td data-value="
|
|
102
|
+
<td data-value="56" class="abs high">56/56</td>
|
|
103
103
|
<td data-value="100" class="pct high">100%</td>
|
|
104
104
|
<td data-value="16" class="abs high">16/16</td>
|
|
105
105
|
<td data-value="100" class="pct high">100%</td>
|
|
106
106
|
<td data-value="5" class="abs high">5/5</td>
|
|
107
107
|
<td data-value="100" class="pct high">100%</td>
|
|
108
|
-
<td data-value="
|
|
108
|
+
<td data-value="54" class="abs high">54/54</td>
|
|
109
109
|
</tr>
|
|
110
110
|
|
|
111
111
|
<tr>
|
|
@@ -129,13 +129,13 @@
|
|
|
129
129
|
<div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div>
|
|
130
130
|
</td>
|
|
131
131
|
<td data-value="100" class="pct high">100%</td>
|
|
132
|
-
<td data-value="
|
|
132
|
+
<td data-value="43" class="abs high">43/43</td>
|
|
133
133
|
<td data-value="100" class="pct high">100%</td>
|
|
134
|
-
<td data-value="
|
|
134
|
+
<td data-value="20" class="abs high">20/20</td>
|
|
135
135
|
<td data-value="100" class="pct high">100%</td>
|
|
136
|
-
<td data-value="
|
|
136
|
+
<td data-value="11" class="abs high">11/11</td>
|
|
137
137
|
<td data-value="100" class="pct high">100%</td>
|
|
138
|
-
<td data-value="
|
|
138
|
+
<td data-value="42" class="abs high">42/42</td>
|
|
139
139
|
</tr>
|
|
140
140
|
|
|
141
141
|
</tbody>
|
|
@@ -146,7 +146,7 @@
|
|
|
146
146
|
<div class='footer quiet pad2 space-top1 center small'>
|
|
147
147
|
Code coverage generated by
|
|
148
148
|
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
|
149
|
-
at 2026-01-
|
|
149
|
+
at 2026-01-18T15:13:46.741Z
|
|
150
150
|
</div>
|
|
151
151
|
<script src="../prettify.js"></script>
|
|
152
152
|
<script>
|
|
@@ -87,25 +87,48 @@ export class TaskGraphValidator {
|
|
|
87
87
|
const errorDetails = result.errors.map(e => e.message);
|
|
88
88
|
return `Task graph validation failed: ${errorDetails.join("; ")}`;
|
|
89
89
|
}
|
|
90
|
-
detectCycle(
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
90
|
+
detectCycle(startTaskId, path, visited, recursionStack, adjacencyList) {
|
|
91
|
+
// Use an explicit stack to avoid maximum call stack size exceeded errors
|
|
92
|
+
const stack = [];
|
|
93
|
+
visited.add(startTaskId);
|
|
94
|
+
recursionStack.add(startTaskId);
|
|
95
|
+
path.push(startTaskId);
|
|
96
|
+
stack.push({
|
|
97
|
+
taskId: startTaskId,
|
|
98
|
+
index: 0,
|
|
99
|
+
/* v8 ignore next */
|
|
100
|
+
dependencies: adjacencyList.get(startTaskId) ?? []
|
|
101
|
+
});
|
|
102
|
+
while (stack.length > 0) {
|
|
103
|
+
const frame = stack[stack.length - 1];
|
|
104
|
+
const { taskId, dependencies } = frame;
|
|
105
|
+
if (frame.index < dependencies.length) {
|
|
106
|
+
const dependenceId = dependencies[frame.index];
|
|
107
|
+
frame.index++;
|
|
108
|
+
if (recursionStack.has(dependenceId)) {
|
|
109
|
+
// Cycle detected
|
|
110
|
+
path.push(dependenceId);
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
if (!visited.has(dependenceId)) {
|
|
114
|
+
visited.add(dependenceId);
|
|
115
|
+
recursionStack.add(dependenceId);
|
|
116
|
+
path.push(dependenceId);
|
|
117
|
+
stack.push({
|
|
118
|
+
taskId: dependenceId,
|
|
119
|
+
index: 0,
|
|
120
|
+
/* v8 ignore next */
|
|
121
|
+
dependencies: adjacencyList.get(dependenceId) ?? []
|
|
122
|
+
});
|
|
123
|
+
}
|
|
99
124
|
}
|
|
100
|
-
else
|
|
101
|
-
//
|
|
102
|
-
|
|
103
|
-
path.
|
|
104
|
-
|
|
125
|
+
else {
|
|
126
|
+
// Finished all dependencies for this node
|
|
127
|
+
recursionStack.delete(taskId);
|
|
128
|
+
path.pop();
|
|
129
|
+
stack.pop();
|
|
105
130
|
}
|
|
106
131
|
}
|
|
107
|
-
recursionStack.delete(taskId);
|
|
108
|
-
path.pop();
|
|
109
132
|
return false;
|
|
110
133
|
}
|
|
111
134
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TaskGraphValidator.js","sourceRoot":"","sources":["../src/TaskGraphValidator.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"TaskGraphValidator.js","sourceRoot":"","sources":["../src/TaskGraphValidator.ts"],"names":[],"mappings":"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"}
|
|
@@ -22,9 +22,16 @@ export declare class WorkflowExecutor<TContext> {
|
|
|
22
22
|
execute(steps: TaskStep<TContext>[]): Promise<Map<string, TaskResult>>;
|
|
23
23
|
/**
|
|
24
24
|
* Logic to identify tasks that can be started or must be skipped.
|
|
25
|
-
* Iterate only over pending steps to avoid O(N^2) checks on completed tasks.
|
|
26
25
|
*/
|
|
27
26
|
private processQueue;
|
|
27
|
+
/**
|
|
28
|
+
* Identifies steps that cannot run because a dependency failed.
|
|
29
|
+
*/
|
|
30
|
+
private handleSkippedTasks;
|
|
31
|
+
/**
|
|
32
|
+
* Returns steps where all dependencies have finished successfully.
|
|
33
|
+
*/
|
|
34
|
+
private getReadySteps;
|
|
28
35
|
/**
|
|
29
36
|
* Handles the lifecycle of a single task execution.
|
|
30
37
|
*/
|
package/dist/WorkflowExecutor.js
CHANGED
|
@@ -23,40 +23,38 @@ export class WorkflowExecutor {
|
|
|
23
23
|
this.eventBus.emit("workflowStart", { context: this.context, steps });
|
|
24
24
|
const results = new Map();
|
|
25
25
|
const executingPromises = new Set();
|
|
26
|
-
const pendingSteps = new Set(steps);
|
|
27
26
|
// Initial pass
|
|
28
|
-
this.processQueue(
|
|
27
|
+
this.processQueue(steps, results, executingPromises);
|
|
29
28
|
while (results.size < steps.length && executingPromises.size > 0) {
|
|
30
29
|
// Wait for the next task to finish
|
|
31
30
|
await Promise.race(executingPromises);
|
|
32
31
|
// After a task finishes, check for new work
|
|
33
|
-
this.processQueue(
|
|
32
|
+
this.processQueue(steps, results, executingPromises);
|
|
34
33
|
}
|
|
35
34
|
this.eventBus.emit("workflowEnd", { context: this.context, results });
|
|
36
35
|
return results;
|
|
37
36
|
}
|
|
38
37
|
/**
|
|
39
38
|
* Logic to identify tasks that can be started or must be skipped.
|
|
40
|
-
* Iterate only over pending steps to avoid O(N^2) checks on completed tasks.
|
|
41
39
|
*/
|
|
42
|
-
processQueue(
|
|
43
|
-
|
|
44
|
-
const
|
|
40
|
+
processQueue(steps, results, executingPromises) {
|
|
41
|
+
this.handleSkippedTasks(steps, results);
|
|
42
|
+
const readySteps = this.getReadySteps(steps, results);
|
|
43
|
+
for (const step of readySteps) {
|
|
44
|
+
const taskPromise = this.runStep(step, results).then(() => {
|
|
45
|
+
executingPromises.delete(taskPromise);
|
|
46
|
+
});
|
|
47
|
+
executingPromises.add(taskPromise);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Identifies steps that cannot run because a dependency failed.
|
|
52
|
+
*/
|
|
53
|
+
handleSkippedTasks(steps, results) {
|
|
54
|
+
const pendingSteps = steps.filter((step) => !results.has(step.name) && !this.running.has(step.name));
|
|
45
55
|
for (const step of pendingSteps) {
|
|
46
56
|
const deps = step.dependencies ?? [];
|
|
47
|
-
|
|
48
|
-
let failedDep;
|
|
49
|
-
for (const dep of deps) {
|
|
50
|
-
const depResult = results.get(dep);
|
|
51
|
-
if (!depResult) {
|
|
52
|
-
// Dependency not finished yet
|
|
53
|
-
blocked = true;
|
|
54
|
-
}
|
|
55
|
-
else if (depResult.status !== "success") {
|
|
56
|
-
failedDep = dep;
|
|
57
|
-
break;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
57
|
+
const failedDep = deps.find((dep) => results.has(dep) && results.get(dep)?.status !== "success");
|
|
60
58
|
if (failedDep) {
|
|
61
59
|
const result = {
|
|
62
60
|
status: "skipped",
|
|
@@ -64,24 +62,19 @@ export class WorkflowExecutor {
|
|
|
64
62
|
};
|
|
65
63
|
results.set(step.name, result);
|
|
66
64
|
this.eventBus.emit("taskSkipped", { step, result });
|
|
67
|
-
toRemove.push(step);
|
|
68
|
-
}
|
|
69
|
-
else if (!blocked) {
|
|
70
|
-
toRun.push(step);
|
|
71
|
-
toRemove.push(step);
|
|
72
65
|
}
|
|
73
66
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Returns steps where all dependencies have finished successfully.
|
|
70
|
+
*/
|
|
71
|
+
getReadySteps(steps, results) {
|
|
72
|
+
return steps.filter((step) => {
|
|
73
|
+
if (results.has(step.name) || this.running.has(step.name))
|
|
74
|
+
return false;
|
|
75
|
+
const deps = step.dependencies ?? [];
|
|
76
|
+
return deps.every((dep) => results.has(dep) && results.get(dep)?.status === "success");
|
|
77
|
+
});
|
|
85
78
|
}
|
|
86
79
|
/**
|
|
87
80
|
* Handles the lifecycle of a single task execution.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WorkflowExecutor.js","sourceRoot":"","sources":["../src/WorkflowExecutor.ts"],"names":[],"mappings":"AAIA;;;GAGG;AACH,MAAM,OAAO,gBAAgB;IAQjB;IACA;IARF,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAEpC;;;OAGG;IACH,YACU,OAAiB,EACjB,QAA4B;QAD5B,YAAO,GAAP,OAAO,CAAU;QACjB,aAAQ,GAAR,QAAQ,CAAoB;IACnC,CAAC;IAEJ;;;;OAIG;IACH,KAAK,CAAC,OAAO,CAAC,KAA2B;QACvC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,eAAe,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;QAEtE,MAAM,OAAO,GAAG,IAAI,GAAG,EAAsB,CAAC;QAC9C,MAAM,iBAAiB,GAAG,IAAI,GAAG,EAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"WorkflowExecutor.js","sourceRoot":"","sources":["../src/WorkflowExecutor.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -104,33 +104,60 @@ export class TaskGraphValidator implements ITaskGraphValidator {
|
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
private detectCycle(
|
|
107
|
-
|
|
107
|
+
startTaskId: string,
|
|
108
108
|
path: string[],
|
|
109
109
|
visited: Set<string>,
|
|
110
110
|
recursionStack: Set<string>,
|
|
111
111
|
adjacencyList: Map<string, string[]>
|
|
112
112
|
): boolean {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
113
|
+
// Use an explicit stack to avoid maximum call stack size exceeded errors
|
|
114
|
+
const stack: { taskId: string; index: number; dependencies: string[] }[] = [];
|
|
115
|
+
|
|
116
|
+
visited.add(startTaskId);
|
|
117
|
+
recursionStack.add(startTaskId);
|
|
118
|
+
path.push(startTaskId);
|
|
119
|
+
|
|
120
|
+
stack.push({
|
|
121
|
+
taskId: startTaskId,
|
|
122
|
+
index: 0,
|
|
123
|
+
/* v8 ignore next */
|
|
124
|
+
dependencies: adjacencyList.get(startTaskId) ?? []
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
while (stack.length > 0) {
|
|
128
|
+
const frame = stack[stack.length - 1];
|
|
129
|
+
const { taskId, dependencies } = frame;
|
|
130
|
+
|
|
131
|
+
if (frame.index < dependencies.length) {
|
|
132
|
+
const dependenceId = dependencies[frame.index];
|
|
133
|
+
frame.index++;
|
|
134
|
+
|
|
135
|
+
if (recursionStack.has(dependenceId)) {
|
|
136
|
+
// Cycle detected
|
|
137
|
+
path.push(dependenceId);
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (!visited.has(dependenceId)) {
|
|
142
|
+
visited.add(dependenceId);
|
|
143
|
+
recursionStack.add(dependenceId);
|
|
144
|
+
path.push(dependenceId);
|
|
145
|
+
|
|
146
|
+
stack.push({
|
|
147
|
+
taskId: dependenceId,
|
|
148
|
+
index: 0,
|
|
149
|
+
/* v8 ignore next */
|
|
150
|
+
dependencies: adjacencyList.get(dependenceId) ?? []
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
} else {
|
|
154
|
+
// Finished all dependencies for this node
|
|
155
|
+
recursionStack.delete(taskId);
|
|
156
|
+
path.pop();
|
|
157
|
+
stack.pop();
|
|
129
158
|
}
|
|
130
159
|
}
|
|
131
160
|
|
|
132
|
-
recursionStack.delete(taskId);
|
|
133
|
-
path.pop();
|
|
134
161
|
return false;
|
|
135
162
|
}
|
|
136
163
|
}
|
package/src/WorkflowExecutor.ts
CHANGED
|
@@ -28,16 +28,15 @@ export class WorkflowExecutor<TContext> {
|
|
|
28
28
|
|
|
29
29
|
const results = new Map<string, TaskResult>();
|
|
30
30
|
const executingPromises = new Set<Promise<void>>();
|
|
31
|
-
const pendingSteps = new Set(steps);
|
|
32
31
|
|
|
33
32
|
// Initial pass
|
|
34
|
-
this.processQueue(
|
|
33
|
+
this.processQueue(steps, results, executingPromises);
|
|
35
34
|
|
|
36
35
|
while (results.size < steps.length && executingPromises.size > 0) {
|
|
37
36
|
// Wait for the next task to finish
|
|
38
37
|
await Promise.race(executingPromises);
|
|
39
38
|
// After a task finishes, check for new work
|
|
40
|
-
this.processQueue(
|
|
39
|
+
this.processQueue(steps, results, executingPromises);
|
|
41
40
|
}
|
|
42
41
|
|
|
43
42
|
this.eventBus.emit("workflowEnd", { context: this.context, results });
|
|
@@ -46,31 +45,37 @@ export class WorkflowExecutor<TContext> {
|
|
|
46
45
|
|
|
47
46
|
/**
|
|
48
47
|
* Logic to identify tasks that can be started or must be skipped.
|
|
49
|
-
* Iterate only over pending steps to avoid O(N^2) checks on completed tasks.
|
|
50
48
|
*/
|
|
51
49
|
private processQueue(
|
|
52
|
-
|
|
50
|
+
steps: TaskStep<TContext>[],
|
|
53
51
|
results: Map<string, TaskResult>,
|
|
54
52
|
executingPromises: Set<Promise<void>>
|
|
55
53
|
): void {
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
this.handleSkippedTasks(steps, results);
|
|
55
|
+
|
|
56
|
+
const readySteps = this.getReadySteps(steps, results);
|
|
57
|
+
|
|
58
|
+
for (const step of readySteps) {
|
|
59
|
+
const taskPromise = this.runStep(step, results).then(() => {
|
|
60
|
+
executingPromises.delete(taskPromise);
|
|
61
|
+
});
|
|
62
|
+
executingPromises.add(taskPromise);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Identifies steps that cannot run because a dependency failed.
|
|
68
|
+
*/
|
|
69
|
+
private handleSkippedTasks(steps: TaskStep<TContext>[], results: Map<string, TaskResult>): void {
|
|
70
|
+
const pendingSteps = steps.filter(
|
|
71
|
+
(step) => !results.has(step.name) && !this.running.has(step.name)
|
|
72
|
+
);
|
|
58
73
|
|
|
59
74
|
for (const step of pendingSteps) {
|
|
60
75
|
const deps = step.dependencies ?? [];
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
for (const dep of deps) {
|
|
65
|
-
const depResult = results.get(dep);
|
|
66
|
-
if (!depResult) {
|
|
67
|
-
// Dependency not finished yet
|
|
68
|
-
blocked = true;
|
|
69
|
-
} else if (depResult.status !== "success") {
|
|
70
|
-
failedDep = dep;
|
|
71
|
-
break;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
76
|
+
const failedDep = deps.find(
|
|
77
|
+
(dep) => results.has(dep) && results.get(dep)?.status !== "success"
|
|
78
|
+
);
|
|
74
79
|
|
|
75
80
|
if (failedDep) {
|
|
76
81
|
const result: TaskResult = {
|
|
@@ -79,25 +84,22 @@ export class WorkflowExecutor<TContext> {
|
|
|
79
84
|
};
|
|
80
85
|
results.set(step.name, result);
|
|
81
86
|
this.eventBus.emit("taskSkipped", { step, result });
|
|
82
|
-
toRemove.push(step);
|
|
83
|
-
} else if (!blocked) {
|
|
84
|
-
toRun.push(step);
|
|
85
|
-
toRemove.push(step);
|
|
86
87
|
}
|
|
87
88
|
}
|
|
89
|
+
}
|
|
88
90
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
91
|
+
/**
|
|
92
|
+
* Returns steps where all dependencies have finished successfully.
|
|
93
|
+
*/
|
|
94
|
+
private getReadySteps(steps: TaskStep<TContext>[], results: Map<string, TaskResult>): TaskStep<TContext>[] {
|
|
95
|
+
return steps.filter((step) => {
|
|
96
|
+
if (results.has(step.name) || this.running.has(step.name)) return false;
|
|
93
97
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
executingPromises.add(taskPromise);
|
|
100
|
-
}
|
|
98
|
+
const deps = step.dependencies ?? [];
|
|
99
|
+
return deps.every(
|
|
100
|
+
(dep) => results.has(dep) && results.get(dep)?.status === "success"
|
|
101
|
+
);
|
|
102
|
+
});
|
|
101
103
|
}
|
|
102
104
|
|
|
103
105
|
/**
|