@calmo/task-runner 4.2.0 → 4.3.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/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +24 -0
- package/dist/EventBus.js +3 -2
- package/dist/EventBus.js.map +1 -1
- package/dist/PluginManager.d.ts +1 -1
- package/dist/PluginManager.js +5 -4
- package/dist/PluginManager.js.map +1 -1
- package/dist/TaskRunner.js +18 -16
- package/dist/TaskRunner.js.map +1 -1
- package/dist/TaskStateManager.d.ts +1 -0
- package/dist/TaskStateManager.js +13 -1
- package/dist/TaskStateManager.js.map +1 -1
- package/dist/TaskStep.d.ts +12 -0
- package/dist/strategies/DryRunExecutionStrategy.js +2 -2
- package/dist/strategies/DryRunExecutionStrategy.js.map +1 -1
- package/dist/strategies/StandardExecutionStrategy.js +43 -1
- package/dist/strategies/StandardExecutionStrategy.js.map +1 -1
- package/openspec/changes/feat-task-caching/design.md +34 -0
- package/openspec/changes/feat-task-caching/proposal.md +18 -0
- package/openspec/changes/feat-task-caching/specs/task-runner/spec.md +58 -0
- package/openspec/changes/feat-task-caching/tasks.md +24 -0
- package/openspec/proposals/feat-matrix-execution/proposal.md +23 -0
- package/openspec/proposals/feat-matrix-execution/specs/task-runner/spec.md +47 -0
- package/openspec/proposals/feat-matrix-execution/tasks.md +11 -0
- package/openspec/proposals/feat-task-observability/proposal.md +16 -0
- package/openspec/proposals/feat-task-observability/specs/task-runner/spec.md +14 -0
- package/openspec/proposals/feat-task-observability/tasks.md +7 -0
- package/package.json +1 -1
- package/src/EventBus.ts +4 -7
- package/src/PluginManager.ts +6 -4
- package/src/TaskRunner.ts +20 -16
- package/src/TaskStateManager.ts +11 -1
- package/src/TaskStep.ts +14 -0
- package/src/strategies/DryRunExecutionStrategy.ts +2 -2
- package/src/strategies/StandardExecutionStrategy.ts +48 -1
- /package/openspec/changes/{feat-continue-on-error → archive/2026-02-18-feat-continue-on-error}/proposal.md +0 -0
- /package/openspec/changes/{feat-continue-on-error → archive/2026-02-18-feat-continue-on-error}/tasks.md +0 -0
- /package/openspec/changes/{feat-per-task-timeout → archive/2026-02-25-feat-per-task-timeout}/proposal.md +0 -0
- /package/openspec/changes/{feat-per-task-timeout → archive/2026-02-25-feat-per-task-timeout}/specs/task-runner/spec.md +0 -0
- /package/openspec/changes/{feat-per-task-timeout → archive/2026-02-25-feat-per-task-timeout}/tasks.md +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -18,6 +18,30 @@
|
|
|
18
18
|
* refactor: Refactor TaskRunner to reduce cognitive complexity (#89) ([95c67d9](https://github.com/thalesraymond/task-runner/commit/95c67d9)), closes [#89](https://github.com/thalesraymond/task-runner/issues/89)
|
|
19
19
|
* Refactor TaskGraphValidator to address SonarCloud issues (#88) ([77c1538](https://github.com/thalesraymond/task-runner/commit/77c1538)), closes [#88](https://github.com/thalesraymond/task-runner/issues/88)
|
|
20
20
|
|
|
21
|
+
## [4.3.0](https://github.com/thalesraymond/task-runner/compare/task-runner-v4.2.0...task-runner-v4.3.0) (2026-03-14)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
### Features
|
|
25
|
+
|
|
26
|
+
* add continueOnError support ([#164](https://github.com/thalesraymond/task-runner/issues/164)) ([50dfb38](https://github.com/thalesraymond/task-runner/commit/50dfb3877b0edd1f233435b2d0a9c9046b30b94d))
|
|
27
|
+
* add per-task timeout support ([#174](https://github.com/thalesraymond/task-runner/issues/174)) ([c73abf3](https://github.com/thalesraymond/task-runner/commit/c73abf3002c8e33dbc524fe570b79fe6466839a7))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
### Bug Fixes
|
|
31
|
+
|
|
32
|
+
* filter non-promises before calling Promise.all in PluginManager ([#200](https://github.com/thalesraymond/task-runner/issues/200)) ([e4bd215](https://github.com/thalesraymond/task-runner/commit/e4bd215b2fe0223ec59e85346a4162eff77dc138))
|
|
33
|
+
* **strategies:** remove redundant Promise.resolve in DryRunExecutionStrategy ([#193](https://github.com/thalesraymond/task-runner/issues/193)) ([42f6185](https://github.com/thalesraymond/task-runner/commit/42f61858413c3e0c50c2c698251ef1904d30a0da))
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
### Performance Improvements
|
|
37
|
+
|
|
38
|
+
* fix pending Promise memory leak in StandardExecutionStrategy ([#201](https://github.com/thalesraymond/task-runner/issues/201)) ([87223b3](https://github.com/thalesraymond/task-runner/commit/87223b3d7facd067604602ef995bbdcc165e8fd6))
|
|
39
|
+
* optimize getMermaidGraph duplicate tracking ([#188](https://github.com/thalesraymond/task-runner/issues/188)) ([f114024](https://github.com/thalesraymond/task-runner/commit/f11402446732db6180cb9b4620f2c7ee53d47a9f))
|
|
40
|
+
* optimize Mermaid graph generation ([#187](https://github.com/thalesraymond/task-runner/issues/187)) ([9b8117b](https://github.com/thalesraymond/task-runner/commit/9b8117ba53613d1b780886afbfb85d1b9b319fa8))
|
|
41
|
+
* optimize Mermaid graph string generation overhead in TaskRunner ([#189](https://github.com/thalesraymond/task-runner/issues/189)) ([513f343](https://github.com/thalesraymond/task-runner/commit/513f343ba2bfdb5a4c362a55da81d26741e5bb6c))
|
|
42
|
+
* parallelize plugin initialization in PluginManager ([#171](https://github.com/thalesraymond/task-runner/issues/171)) ([d39781f](https://github.com/thalesraymond/task-runner/commit/d39781fd1af28d2ffeb320745a386c95d74bd3f3))
|
|
43
|
+
* **TaskStateManager:** optimize ready queue copy ([#179](https://github.com/thalesraymond/task-runner/issues/179)) ([ea77aa0](https://github.com/thalesraymond/task-runner/commit/ea77aa0afc8da5a55730216d081f7e1c80f4982e))
|
|
44
|
+
|
|
21
45
|
## [4.2.0](https://github.com/thalesraymond/task-runner/compare/task-runner-v4.1.0...task-runner-v4.2.0) (2026-02-15)
|
|
22
46
|
|
|
23
47
|
|
package/dist/EventBus.js
CHANGED
|
@@ -24,8 +24,9 @@ export class EventBus {
|
|
|
24
24
|
* @param callback The callback to remove.
|
|
25
25
|
*/
|
|
26
26
|
off(event, callback) {
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
const listeners = this.listeners[event];
|
|
28
|
+
if (listeners) {
|
|
29
|
+
listeners.delete(callback);
|
|
29
30
|
}
|
|
30
31
|
}
|
|
31
32
|
/**
|
package/dist/EventBus.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EventBus.js","sourceRoot":"","sources":["../src/EventBus.ts"],"names":[],"mappings":"AAMA;;;GAGG;AACH,MAAM,OAAO,QAAQ;IACX,SAAS,GAA0B,EAAE,CAAC;IAE9C;;;;OAIG;IACI,EAAE,CACP,KAAQ,EACR,QAA0C;QAE1C,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC;YAC3B,4EAA4E;YAC5E,iEAAiE;YACjE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,IAAI,GAAG,EAAyC,CAAC;QAC3E,CAAC;QACD,oFAAoF;QACnF,IAAI,CAAC,SAAS,CAAC,KAAK,CAA2C,CAAC,GAAG,CAClE,QAAQ,CACT,CAAC;IACJ,CAAC;IAED;;;;OAIG;IACI,GAAG,CACR,KAAQ,EACR,QAA0C;QAE1C,
|
|
1
|
+
{"version":3,"file":"EventBus.js","sourceRoot":"","sources":["../src/EventBus.ts"],"names":[],"mappings":"AAMA;;;GAGG;AACH,MAAM,OAAO,QAAQ;IACX,SAAS,GAA0B,EAAE,CAAC;IAE9C;;;;OAIG;IACI,EAAE,CACP,KAAQ,EACR,QAA0C;QAE1C,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC;YAC3B,4EAA4E;YAC5E,iEAAiE;YACjE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,IAAI,GAAG,EAAyC,CAAC;QAC3E,CAAC;QACD,oFAAoF;QACnF,IAAI,CAAC,SAAS,CAAC,KAAK,CAA2C,CAAC,GAAG,CAClE,QAAQ,CACT,CAAC;IACJ,CAAC;IAED;;;;OAIG;IACI,GAAG,CACR,KAAQ,EACR,QAA0C;QAE1C,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QACxC,IAAI,SAAS,EAAE,CAAC;YACd,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC7B,CAAC;IACH,CAAC;IAED;;;;OAIG;IACI,IAAI,CACT,KAAQ,EACR,IAAsC;QAEtC,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QACxC,IAAI,SAAS,EAAE,CAAC;YACd,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;gBACjC,2EAA2E;gBAC3E,iDAAiD;gBACjD,cAAc,CAAC,GAAG,EAAE;oBAClB,IAAI,CAAC;wBACH,IAAI,CAAC;4BACH,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;4BAC9B,IAAI,MAAM,YAAY,OAAO,EAAE,CAAC;gCAC9B,mCAAmC;gCACnC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;oCACrB,OAAO,CAAC,KAAK,CACX,+BAA+B,MAAM,CAAC,KAAK,CAAC,GAAG,EAC/C,KAAK,CACN,CAAC;gCACJ,CAAC,CAAC,CAAC;4BACL,CAAC;wBACH,CAAC;wBAAC,OAAO,KAAK,EAAE,CAAC;4BACf,8BAA8B;4BAC9B,OAAO,CAAC,KAAK,CACX,+BAA+B,MAAM,CAAC,KAAK,CAAC,GAAG,EAC/C,KAAK,CACN,CAAC;wBACJ,CAAC;oBACH,CAAC;oBAAC,OAAO,KAAK,EAAE,CAAC;wBACf,uDAAuD;wBACvD,OAAO,CAAC,KAAK,CACX,+CAA+C,MAAM,CAAC,KAAK,CAAC,GAAG,EAC/D,KAAK,CACN,CAAC;oBACJ,CAAC;gBACH,CAAC,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;CACF"}
|
package/dist/PluginManager.d.ts
CHANGED
package/dist/PluginManager.js
CHANGED
|
@@ -22,12 +22,13 @@ export class PluginManager {
|
|
|
22
22
|
this.plugins.push(plugin);
|
|
23
23
|
}
|
|
24
24
|
/**
|
|
25
|
-
* Initializes all registered plugins.
|
|
25
|
+
* Initializes all registered plugins in parallel.
|
|
26
26
|
*/
|
|
27
27
|
async initialize() {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
const installPromises = this.plugins
|
|
29
|
+
.map((plugin) => plugin.install(this.context))
|
|
30
|
+
.filter((result) => result instanceof Promise);
|
|
31
|
+
await Promise.all(installPromises);
|
|
31
32
|
}
|
|
32
33
|
/**
|
|
33
34
|
* Returns the list of registered plugins.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PluginManager.js","sourceRoot":"","sources":["../src/PluginManager.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,OAAO,aAAa;IAGJ;IAFZ,OAAO,GAAuB,EAAE,CAAC;IAEzC,YAAoB,OAAgC;QAAhC,YAAO,GAAP,OAAO,CAAyB;IAAG,CAAC;IAExD;;;OAGG;IACI,GAAG,CAAC,MAAwB;QACjC,wCAAwC;QACxC,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;YACrD,8CAA8C;YAC9C,2DAA2D;YAC3D,6CAA6C;YAC7C,MAAM,IAAI,KAAK,CAAC,qBAAqB,MAAM,CAAC,IAAI,0BAA0B,CAAC,CAAC;QAC9E,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC5B,CAAC;IAED;;OAEG;IACI,KAAK,CAAC,UAAU;QACrB,
|
|
1
|
+
{"version":3,"file":"PluginManager.js","sourceRoot":"","sources":["../src/PluginManager.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,OAAO,aAAa;IAGJ;IAFZ,OAAO,GAAuB,EAAE,CAAC;IAEzC,YAAoB,OAAgC;QAAhC,YAAO,GAAP,OAAO,CAAyB;IAAG,CAAC;IAExD;;;OAGG;IACI,GAAG,CAAC,MAAwB;QACjC,wCAAwC;QACxC,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;YACrD,8CAA8C;YAC9C,2DAA2D;YAC3D,6CAA6C;YAC7C,MAAM,IAAI,KAAK,CAAC,qBAAqB,MAAM,CAAC,IAAI,0BAA0B,CAAC,CAAC;QAC9E,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC5B,CAAC;IAED;;OAEG;IACI,KAAK,CAAC,UAAU;QACrB,MAAM,eAAe,GAAG,IAAI,CAAC,OAAO;aACjC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;aAC7C,MAAM,CAAC,CAAC,MAAM,EAA2B,EAAE,CAAC,MAAM,YAAY,OAAO,CAAC,CAAC;QAE1E,MAAM,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;IACrC,CAAC;IAED;;OAEG;IACI,UAAU;QACf,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;CACF"}
|
package/dist/TaskRunner.js
CHANGED
|
@@ -65,7 +65,8 @@ export class TaskRunner {
|
|
|
65
65
|
* @returns A string containing the Mermaid graph definition.
|
|
66
66
|
*/
|
|
67
67
|
static getMermaidGraph(steps) {
|
|
68
|
-
const
|
|
68
|
+
const nodeLines = ["graph TD"];
|
|
69
|
+
const edgeLines = new Set();
|
|
69
70
|
const idMap = new Map();
|
|
70
71
|
const usedIds = new Set();
|
|
71
72
|
const baseIdCounters = new Map();
|
|
@@ -93,26 +94,27 @@ export class TaskRunner {
|
|
|
93
94
|
idMap.set(name, uniqueId);
|
|
94
95
|
return uniqueId;
|
|
95
96
|
};
|
|
96
|
-
//
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
97
|
+
// Process nodes and edges in a single pass over input steps
|
|
98
|
+
const processedNodes = new Set();
|
|
99
|
+
for (let i = 0; i < steps.length; i++) {
|
|
100
|
+
const step = steps[i];
|
|
101
|
+
const name = step.name;
|
|
102
|
+
const stepId = getUniqueId(name);
|
|
103
|
+
const sizeBefore = processedNodes.size;
|
|
104
|
+
processedNodes.add(stepId);
|
|
105
|
+
if (processedNodes.size !== sizeBefore) {
|
|
106
|
+
const escapedName = name.replaceAll("\"", """);
|
|
107
|
+
nodeLines.push(` ${stepId}["${escapedName}"]`);
|
|
104
108
|
}
|
|
105
|
-
}
|
|
106
|
-
for (const step of steps) {
|
|
107
109
|
if (step.dependencies) {
|
|
108
|
-
const
|
|
109
|
-
for (
|
|
110
|
-
const depId = getUniqueId(
|
|
111
|
-
|
|
110
|
+
const deps = step.dependencies;
|
|
111
|
+
for (let j = 0; j < deps.length; j++) {
|
|
112
|
+
const depId = getUniqueId(deps[j]);
|
|
113
|
+
edgeLines.add(` ${depId} --> ${stepId}`);
|
|
112
114
|
}
|
|
113
115
|
}
|
|
114
116
|
}
|
|
115
|
-
return [...
|
|
117
|
+
return nodeLines.concat([...edgeLines]).join("\n");
|
|
116
118
|
}
|
|
117
119
|
/**
|
|
118
120
|
* Sanitizes a string for use as a Mermaid node ID.
|
package/dist/TaskRunner.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TaskRunner.js","sourceRoot":"","sources":["../src/TaskRunner.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAM7D,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAEzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAEzE,OAAO,EAAE,yBAAyB,EAAE,MAAM,2CAA2C,CAAC;AACtF,OAAO,EAAE,yBAAyB,EAAE,MAAM,2CAA2C,CAAC;AAEtF,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,uBAAuB,EAAE,MAAM,yCAAyC,CAAC;AAElF;;;;GAIG;AACH,MAAM,OAAO,UAAU;IAWD;IAVZ,QAAQ,GAAG,IAAI,QAAQ,EAAY,CAAC;IACpC,SAAS,GAAG,IAAI,kBAAkB,EAAE,CAAC;IACrC,iBAAiB,GACvB,IAAI,yBAAyB,CAAC,IAAI,yBAAyB,EAAE,CAAC,CAAC;IAEzD,aAAa,CAA0B;IAE/C;;OAEG;IACH,YAAoB,OAAiB;QAAjB,YAAO,GAAP,OAAO,CAAU;QACnC,IAAI,CAAC,aAAa,GAAG,IAAI,aAAa,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;IACpE,CAAC;IAED;;;;OAIG;IACI,EAAE,CACP,KAAQ,EACR,QAA0C;QAE1C,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IACpC,CAAC;IAED;;;;OAIG;IACI,GAAG,CACR,KAAQ,EACR,QAA0C;QAE1C,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IACrC,CAAC;IAED;;;;OAIG;IACI,GAAG,CAAC,MAAwB;QACjC,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAC/B,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;OAIG;IACI,oBAAoB,CAAC,QAAsC;QAChE,IAAI,CAAC,iBAAiB,GAAG,QAAQ,CAAC;QAClC,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;OAIG;IACI,MAAM,CAAC,eAAe,CAAI,KAAoB;QACnD,MAAM,
|
|
1
|
+
{"version":3,"file":"TaskRunner.js","sourceRoot":"","sources":["../src/TaskRunner.ts"],"names":[],"mappings":"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"}
|
package/dist/TaskStateManager.js
CHANGED
|
@@ -11,6 +11,7 @@ export class TaskStateManager {
|
|
|
11
11
|
dependencyGraph = new Map();
|
|
12
12
|
dependencyCounts = new Map();
|
|
13
13
|
readyQueue = [];
|
|
14
|
+
taskDefinitions = new Map();
|
|
14
15
|
constructor(eventBus) {
|
|
15
16
|
this.eventBus = eventBus;
|
|
16
17
|
}
|
|
@@ -25,7 +26,9 @@ export class TaskStateManager {
|
|
|
25
26
|
this.readyQueue = [];
|
|
26
27
|
this.dependencyGraph.clear();
|
|
27
28
|
this.dependencyCounts.clear();
|
|
29
|
+
this.taskDefinitions.clear();
|
|
28
30
|
for (const step of steps) {
|
|
31
|
+
this.taskDefinitions.set(step.name, step);
|
|
29
32
|
const deps = step.dependencies ?? [];
|
|
30
33
|
this.dependencyCounts.set(step.name, deps.length);
|
|
31
34
|
if (deps.length === 0) {
|
|
@@ -49,7 +52,7 @@ export class TaskStateManager {
|
|
|
49
52
|
* @returns An array of tasks that are ready to run.
|
|
50
53
|
*/
|
|
51
54
|
processDependencies() {
|
|
52
|
-
const toRun =
|
|
55
|
+
const toRun = this.readyQueue;
|
|
53
56
|
this.readyQueue = [];
|
|
54
57
|
// Remove them from pendingSteps as they are now handed off to the executor
|
|
55
58
|
for (const step of toRun) {
|
|
@@ -78,6 +81,15 @@ export class TaskStateManager {
|
|
|
78
81
|
if (result.status === "success") {
|
|
79
82
|
this.handleSuccess(step.name);
|
|
80
83
|
}
|
|
84
|
+
else if (result.status === "failure") {
|
|
85
|
+
// If continueOnError is true, treat as success for dependents to unblock the workflow
|
|
86
|
+
if (this.taskDefinitions.get(step.name)?.continueOnError) {
|
|
87
|
+
this.handleSuccess(step.name);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
this.cascadeFailure(step.name);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
81
93
|
else {
|
|
82
94
|
this.cascadeFailure(step.name);
|
|
83
95
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TaskStateManager.js","sourceRoot":"","sources":["../src/TaskStateManager.ts"],"names":[],"mappings":"AAIA;;;GAGG;AACH,MAAM,OAAO,gBAAgB;
|
|
1
|
+
{"version":3,"file":"TaskStateManager.js","sourceRoot":"","sources":["../src/TaskStateManager.ts"],"names":[],"mappings":"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"}
|
package/dist/TaskStep.d.ts
CHANGED
|
@@ -22,6 +22,18 @@ export interface TaskStep<TContext> {
|
|
|
22
22
|
* Only affects ordering when multiple tasks are ready and concurrency slots are limited.
|
|
23
23
|
*/
|
|
24
24
|
priority?: number;
|
|
25
|
+
/**
|
|
26
|
+
* Optional flag to indicate that the workflow should continue even if this task fails.
|
|
27
|
+
* If true, dependent tasks will execute as if this task succeeded.
|
|
28
|
+
* The task result will still be marked as "failure".
|
|
29
|
+
* Default is false.
|
|
30
|
+
*/
|
|
31
|
+
continueOnError?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Optional maximum execution time in milliseconds.
|
|
34
|
+
* If the task runs longer than this, it will be cancelled and marked as failed.
|
|
35
|
+
*/
|
|
36
|
+
timeout?: number;
|
|
25
37
|
/**
|
|
26
38
|
* The core logic of the task.
|
|
27
39
|
* @param context The shared context object, allowing for state to be passed between tasks.
|
|
@@ -14,10 +14,10 @@ export class DryRunExecutionStrategy {
|
|
|
14
14
|
_context,
|
|
15
15
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
16
16
|
_signal) {
|
|
17
|
-
return
|
|
17
|
+
return {
|
|
18
18
|
status: "success",
|
|
19
19
|
message: "Dry run: simulated success " + step.name,
|
|
20
|
-
}
|
|
20
|
+
};
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
//# sourceMappingURL=DryRunExecutionStrategy.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DryRunExecutionStrategy.js","sourceRoot":"","sources":["../../src/strategies/DryRunExecutionStrategy.ts"],"names":[],"mappings":"AAIA;;GAEG;AACH,MAAM,OAAO,uBAAuB;IAGlC;;;;;;OAMG;IACH,KAAK,CAAC,OAAO,CACX,IAAwB;IACxB,6DAA6D;IAC7D,QAAkB;IAClB,6DAA6D;IAC7D,OAAqB;QAErB,OAAO
|
|
1
|
+
{"version":3,"file":"DryRunExecutionStrategy.js","sourceRoot":"","sources":["../../src/strategies/DryRunExecutionStrategy.ts"],"names":[],"mappings":"AAIA;;GAEG;AACH,MAAM,OAAO,uBAAuB;IAGlC;;;;;;OAMG;IACH,KAAK,CAAC,OAAO,CACX,IAAwB;IACxB,6DAA6D;IAC7D,QAAkB;IAClB,6DAA6D;IAC7D,OAAqB;QAErB,OAAO;YACL,MAAM,EAAE,SAAS;YACjB,OAAO,EAAE,6BAA6B,GAAG,IAAI,CAAC,IAAI;SACnD,CAAC;IACJ,CAAC;CACF"}
|
|
@@ -3,8 +3,45 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export class StandardExecutionStrategy {
|
|
5
5
|
async execute(step, context, signal) {
|
|
6
|
+
if (!step.timeout) {
|
|
7
|
+
try {
|
|
8
|
+
return await step.run(context, signal);
|
|
9
|
+
}
|
|
10
|
+
catch (e) {
|
|
11
|
+
// Check if error is due to abort
|
|
12
|
+
if (signal?.aborted &&
|
|
13
|
+
((e instanceof Error && e.name === "AbortError") ||
|
|
14
|
+
signal.reason === e)) {
|
|
15
|
+
return {
|
|
16
|
+
status: "cancelled",
|
|
17
|
+
message: "Task cancelled during execution",
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
status: "failure",
|
|
22
|
+
error: e instanceof Error ? e.message : String(e),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
const abortController = new AbortController();
|
|
27
|
+
const timeoutSignal = signal
|
|
28
|
+
? AbortSignal.any([signal, abortController.signal])
|
|
29
|
+
: abortController.signal;
|
|
30
|
+
let timer;
|
|
31
|
+
let resolveTimeout;
|
|
6
32
|
try {
|
|
7
|
-
|
|
33
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
34
|
+
resolveTimeout = resolve;
|
|
35
|
+
timer = setTimeout(() => {
|
|
36
|
+
abortController.abort(new Error("Timeout"));
|
|
37
|
+
resolve({
|
|
38
|
+
status: "failure",
|
|
39
|
+
error: `Task timed out after ${step.timeout}ms`,
|
|
40
|
+
});
|
|
41
|
+
}, step.timeout);
|
|
42
|
+
});
|
|
43
|
+
const taskPromise = step.run(context, timeoutSignal);
|
|
44
|
+
return await Promise.race([taskPromise, timeoutPromise]);
|
|
8
45
|
}
|
|
9
46
|
catch (e) {
|
|
10
47
|
// Check if error is due to abort
|
|
@@ -20,6 +57,11 @@ export class StandardExecutionStrategy {
|
|
|
20
57
|
error: e instanceof Error ? e.message : String(e),
|
|
21
58
|
};
|
|
22
59
|
}
|
|
60
|
+
finally {
|
|
61
|
+
clearTimeout(timer);
|
|
62
|
+
// Settle the timeout promise to avoid memory leaks from Promise.race
|
|
63
|
+
resolveTimeout({ status: "cancelled" });
|
|
64
|
+
}
|
|
23
65
|
}
|
|
24
66
|
}
|
|
25
67
|
//# sourceMappingURL=StandardExecutionStrategy.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StandardExecutionStrategy.js","sourceRoot":"","sources":["../../src/strategies/StandardExecutionStrategy.ts"],"names":[],"mappings":"AAIA;;GAEG;AACH,MAAM,OAAO,yBAAyB;IAGpC,KAAK,CAAC,OAAO,CACX,IAAwB,EACxB,OAAiB,EACjB,MAAoB;QAEpB,IAAI,CAAC;YACH,OAAO,MAAM,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"StandardExecutionStrategy.js","sourceRoot":"","sources":["../../src/strategies/StandardExecutionStrategy.ts"],"names":[],"mappings":"AAIA;;GAEG;AACH,MAAM,OAAO,yBAAyB;IAGpC,KAAK,CAAC,OAAO,CACX,IAAwB,EACxB,OAAiB,EACjB,MAAoB;QAEpB,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YAClB,IAAI,CAAC;gBACH,OAAO,MAAM,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;YACzC,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,iCAAiC;gBACjC,IACE,MAAM,EAAE,OAAO;oBACf,CAAC,CAAC,CAAC,YAAY,KAAK,IAAI,CAAC,CAAC,IAAI,KAAK,YAAY,CAAC;wBAC9C,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,EACtB,CAAC;oBACD,OAAO;wBACL,MAAM,EAAE,WAAW;wBACnB,OAAO,EAAE,iCAAiC;qBAC3C,CAAC;gBACJ,CAAC;gBACD,OAAO;oBACL,MAAM,EAAE,SAAS;oBACjB,KAAK,EAAE,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;iBAClD,CAAC;YACJ,CAAC;QACH,CAAC;QAED,MAAM,eAAe,GAAG,IAAI,eAAe,EAAE,CAAC;QAC9C,MAAM,aAAa,GAAG,MAAM;YAC1B,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,eAAe,CAAC,MAAM,CAAC,CAAC;YACnD,CAAC,CAAC,eAAe,CAAC,MAAM,CAAC;QAE3B,IAAI,KAAiC,CAAC;QACtC,IAAI,cAA4C,CAAC;QAEjD,IAAI,CAAC;YACH,MAAM,cAAc,GAAG,IAAI,OAAO,CAAa,CAAC,OAAO,EAAE,EAAE;gBACzD,cAAc,GAAG,OAAO,CAAC;gBACzB,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;oBACtB,eAAe,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC;oBAC5C,OAAO,CAAC;wBACN,MAAM,EAAE,SAAS;wBACjB,KAAK,EAAE,wBAAwB,IAAI,CAAC,OAAO,IAAI;qBAChD,CAAC,CAAC;gBACL,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;YACnB,CAAC,CAAC,CAAC;YAEH,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;YAErD,OAAO,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,cAAc,CAAC,CAAC,CAAC;QAC3D,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,iCAAiC;YACjC,IACE,MAAM,EAAE,OAAO;gBACf,CAAC,CAAC,CAAC,YAAY,KAAK,IAAI,CAAC,CAAC,IAAI,KAAK,YAAY,CAAC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,EACxE,CAAC;gBACD,OAAO;oBACL,MAAM,EAAE,WAAW;oBACnB,OAAO,EAAE,iCAAiC;iBAC3C,CAAC;YACJ,CAAC;YACD,OAAO;gBACL,MAAM,EAAE,SAAS;gBACjB,KAAK,EAAE,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;aAClD,CAAC;QACJ,CAAC;gBAAS,CAAC;YACT,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,qEAAqE;YACrE,cAAc,CAAC,EAAE,MAAM,EAAE,WAAW,EAAgB,CAAC,CAAC;QACxD,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
## Context
|
|
2
|
+
|
|
3
|
+
Currently, the task runner always re-executes tasks, even if inputs (context) have not changed. This is inefficient for workflows with expensive steps like builds or data processing.
|
|
4
|
+
|
|
5
|
+
## Goals / Non-Goals
|
|
6
|
+
|
|
7
|
+
- **Goals**:
|
|
8
|
+
- Avoid redundant execution of expensive tasks.
|
|
9
|
+
- Support pluggable caching mechanisms (defaulting to in-memory).
|
|
10
|
+
- Allow restoration of context side effects when skipping execution.
|
|
11
|
+
- **Non-Goals**:
|
|
12
|
+
- Distributed caching (out of scope for now).
|
|
13
|
+
- Automatic dependency hashing (cache key must be provided by the user).
|
|
14
|
+
- Persistent file system caching (can be added later via plugin or custom provider).
|
|
15
|
+
|
|
16
|
+
## Decisions
|
|
17
|
+
|
|
18
|
+
- **Decision**: Use `ICacheProvider` interface.
|
|
19
|
+
- **Rationale**: Allows users to swap the caching backend (e.g., Redis, FS) without changing core logic.
|
|
20
|
+
- **Decision**: Explicit `restore` callback.
|
|
21
|
+
- **Rationale**: Since context is mutable and side-effect driven, simply returning a cached result is insufficient. The task must explicitly define how to re-apply its changes to the context based on the cached result.
|
|
22
|
+
- **Decision**: Wrap execution strategy.
|
|
23
|
+
- **Rationale**: Follows the existing decorator pattern (like `RetryingExecutionStrategy`), keeping concerns separated.
|
|
24
|
+
|
|
25
|
+
## Risks / Trade-offs
|
|
26
|
+
|
|
27
|
+
- **Risk**: Stale cache data if keys are not unique enough.
|
|
28
|
+
- **Mitigation**: Documentation must emphasize the importance of including all relevant inputs in the cache key.
|
|
29
|
+
- **Risk**: Context inconsistency if `restore` is implemented incorrectly.
|
|
30
|
+
- **Mitigation**: Provide clear examples and potentially validate context changes in debug mode.
|
|
31
|
+
|
|
32
|
+
## Migration Plan
|
|
33
|
+
|
|
34
|
+
- This is an additive change. Existing tasks without `cache` config will work as before. No migration required.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Change: Task Output Caching
|
|
2
|
+
|
|
3
|
+
## Why
|
|
4
|
+
|
|
5
|
+
Currently, the task runner executes every task on every run, regardless of whether inputs or context have changed. For complex workflows involving expensive operations (e.g., builds, data processing), this leads to redundant execution and slower feedback loops. Implementing a caching mechanism will significantly improve performance by skipping tasks that have already been successfully executed with the same inputs.
|
|
6
|
+
|
|
7
|
+
## What Changes
|
|
8
|
+
|
|
9
|
+
- **Task Configuration**: Add `cache` configuration to `TaskStep` interface, allowing tasks to define a cache key and a restoration logic.
|
|
10
|
+
- **Execution Strategy**: Introduce `CachingExecutionStrategy` that wraps other strategies. It checks for a cached result before execution and stores the result after successful execution.
|
|
11
|
+
- **Cache Provider**: Define an `ICacheProvider` interface with a default in-memory implementation (`MemoryCacheProvider`), allowing for future extension (e.g., file system or remote cache).
|
|
12
|
+
- **Task Result**: Ensure `TaskResult` is serializable and contains necessary metadata for caching.
|
|
13
|
+
|
|
14
|
+
## Impact
|
|
15
|
+
|
|
16
|
+
- **Affected specs**: `task-runner`
|
|
17
|
+
- **Affected code**: `TaskStep.ts`, `TaskRunner.ts`, new strategy `CachingExecutionStrategy.ts`, new contract `ICacheProvider.ts`.
|
|
18
|
+
- **Performance**: Significant reduction in execution time for repeated workflows.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
## ADDED Requirements
|
|
2
|
+
|
|
3
|
+
### Requirement: Task Caching Configuration
|
|
4
|
+
|
|
5
|
+
The `TaskStep` interface SHALL support an optional `cache` property of type `TaskCacheConfig`.
|
|
6
|
+
|
|
7
|
+
#### Scenario: Cache Config Structure
|
|
8
|
+
|
|
9
|
+
- **GIVEN** a `TaskCacheConfig` object
|
|
10
|
+
- **THEN** it SHALL support:
|
|
11
|
+
- `key`: A function returning a unique string key based on the context.
|
|
12
|
+
- `ttl`: Optional time-to-live in milliseconds.
|
|
13
|
+
- `restore`: Optional function to restore context side effects from a cached result.
|
|
14
|
+
|
|
15
|
+
### Requirement: Caching Execution Strategy
|
|
16
|
+
|
|
17
|
+
The system SHALL provide a `CachingExecutionStrategy` that implements `IExecutionStrategy` and wraps another `IExecutionStrategy`.
|
|
18
|
+
|
|
19
|
+
#### Scenario: Cache Miss Execution
|
|
20
|
+
|
|
21
|
+
- **WHEN** the `CachingExecutionStrategy` executes a task with a cache key that is NOT present in the cache provider
|
|
22
|
+
- **THEN** it SHALL execute the task using the inner strategy.
|
|
23
|
+
- **AND** it SHALL store the result in the cache provider if execution is successful.
|
|
24
|
+
- **AND** it SHALL return the result.
|
|
25
|
+
|
|
26
|
+
#### Scenario: Cache Hit Execution
|
|
27
|
+
|
|
28
|
+
- **WHEN** the `CachingExecutionStrategy` executes a task with a cache key that IS present in the cache provider
|
|
29
|
+
- **THEN** it SHALL NOT execute the inner strategy.
|
|
30
|
+
- **AND** it SHALL invoke the `restore` function (if provided) with the current context and the cached result.
|
|
31
|
+
- **AND** it SHALL return the cached result.
|
|
32
|
+
|
|
33
|
+
#### Scenario: Cache Expiration
|
|
34
|
+
|
|
35
|
+
- **WHEN** a cached item's TTL has expired
|
|
36
|
+
- **THEN** the cache provider SHALL NOT return the item.
|
|
37
|
+
- **AND** the strategy SHALL proceed as a cache miss.
|
|
38
|
+
|
|
39
|
+
### Requirement: Cache Provider Interface
|
|
40
|
+
|
|
41
|
+
The system SHALL define an `ICacheProvider` interface for pluggable caching backends.
|
|
42
|
+
|
|
43
|
+
#### Scenario: Interface Methods
|
|
44
|
+
|
|
45
|
+
- **GIVEN** an `ICacheProvider` implementation
|
|
46
|
+
- **THEN** it SHALL support:
|
|
47
|
+
- `get(key: string): Promise<TaskResult | undefined>`
|
|
48
|
+
- `set(key: string, value: TaskResult, ttl?: number): Promise<void>`
|
|
49
|
+
- `delete(key: string): Promise<void>`
|
|
50
|
+
|
|
51
|
+
### Requirement: Default Memory Cache
|
|
52
|
+
|
|
53
|
+
The system SHALL provide a `MemoryCacheProvider` as the default implementation of `ICacheProvider`.
|
|
54
|
+
|
|
55
|
+
#### Scenario: In-Memory Storage
|
|
56
|
+
|
|
57
|
+
- **WHEN** items are set in `MemoryCacheProvider`
|
|
58
|
+
- **THEN** they are stored in memory and retrieved correctly until process termination or expiration.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
## 1. Implementation
|
|
2
|
+
|
|
3
|
+
- [ ] 1.1 Define `ICacheProvider` interface in `src/contracts/ICacheProvider.ts` with `get(key)`, `set(key, result, ttl)`, and `delete(key)` methods.
|
|
4
|
+
- [ ] 1.2 Implement `MemoryCacheProvider` in `src/utils/MemoryCacheProvider.ts` as the default in-memory cache implementation.
|
|
5
|
+
- [ ] 1.3 Update `TaskStep` interface in `src/TaskStep.ts` to include optional `cache` configuration:
|
|
6
|
+
- `key`: `(context: TContext) => string | Promise<string>`
|
|
7
|
+
- `ttl`: `number` (optional, default to infinite)
|
|
8
|
+
- `restore`: `(context: TContext, cachedResult: TaskResult) => void | Promise<void>` (optional, to re-apply context side effects)
|
|
9
|
+
- [ ] 1.4 Create `CachingExecutionStrategy` in `src/strategies/CachingExecutionStrategy.ts`.
|
|
10
|
+
- It should implement `IExecutionStrategy`.
|
|
11
|
+
- It should accept an inner `IExecutionStrategy` and an `ICacheProvider`.
|
|
12
|
+
- In `execute`:
|
|
13
|
+
- Calculate cache key using `step.cache.key(context)`.
|
|
14
|
+
- Check cache provider. If hit:
|
|
15
|
+
- Execute `step.cache.restore(context, result)` if provided.
|
|
16
|
+
- Return cached result with status `skipped` (or a new status `cached`).
|
|
17
|
+
- If miss:
|
|
18
|
+
- Execute inner strategy.
|
|
19
|
+
- If successful, store result in cache provider using `ttl`.
|
|
20
|
+
- Return result.
|
|
21
|
+
- [ ] 1.5 Update `TaskRunner.ts` to support configuring the cache provider and wrapping the execution strategy with `CachingExecutionStrategy` if caching is enabled.
|
|
22
|
+
- [ ] 1.6 Add unit tests for `MemoryCacheProvider`.
|
|
23
|
+
- [ ] 1.7 Add unit tests for `CachingExecutionStrategy`, verifying cache hits, misses, and restoration of context.
|
|
24
|
+
- [ ] 1.8 Add integration tests in `tests/TaskRunnerCaching.test.ts` to verify end-to-end caching behavior with context updates.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Change: Add Matrix Execution Support
|
|
2
|
+
|
|
3
|
+
## Why
|
|
4
|
+
|
|
5
|
+
Task orchestration tools in the industry (such as Nx, GitHub Actions, GitLab CI, and Turborepo) commonly support Matrix Execution. This allows a single task definition to run multiple times automatically across a set of variable combinations. In `task-runner`, defining identical tasks to process multiple permutations manually increases boilerplate and violates the DRY (Don't Repeat Yourself) principle. Introducing `matrix` configurations directly on the `TaskStep` allows developers to run a task concurrently across different configurations (e.g., Node.js versions, OS platforms, target environments) efficiently.
|
|
6
|
+
|
|
7
|
+
## What Changes
|
|
8
|
+
|
|
9
|
+
- Introduce a `matrix` property to the `TaskStep` configuration.
|
|
10
|
+
- The `matrix` property should support defining variables as an object of arrays (e.g., `matrix: { node: [18, 20], os: ['ubuntu', 'windows'] }`).
|
|
11
|
+
- The engine will dynamically generate and execute independent child tasks for each permutation of the matrix.
|
|
12
|
+
- Ensure the `MermaidGraph` utility correctly visualizes these dynamic nodes.
|
|
13
|
+
- Expose the current permutation variables to the execution context of each child task.
|
|
14
|
+
|
|
15
|
+
## Impact
|
|
16
|
+
|
|
17
|
+
- Affected specs: `task-runner` (Task Configuration, Core Orchestration)
|
|
18
|
+
- Affected code:
|
|
19
|
+
- `src/core/TaskStep.ts`
|
|
20
|
+
- `src/core/TaskRunner.ts`
|
|
21
|
+
- `src/core/TaskGraph.ts`
|
|
22
|
+
- `src/core/TaskStateManager.ts`
|
|
23
|
+
- `src/core/WorkflowExecutor.ts`
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
## ADDED Requirements
|
|
2
|
+
|
|
3
|
+
### Requirement: Matrix Execution Configuration
|
|
4
|
+
|
|
5
|
+
The `TaskStep` interface SHALL support an optional `matrix` configuration property to enable parameterized execution.
|
|
6
|
+
|
|
7
|
+
#### Scenario: Matrix configuration structure
|
|
8
|
+
|
|
9
|
+
- **GIVEN** a `TaskStep` definition
|
|
10
|
+
- **THEN** it SHALL support a `matrix` property defined as a mapping of variable names to arrays of values (e.g., `Record<string, any[]>`).
|
|
11
|
+
|
|
12
|
+
### Requirement: Matrix Permutation Expansion
|
|
13
|
+
|
|
14
|
+
The `TaskRunner` engine SHALL dynamically expand a task with a `matrix` configuration into multiple independent task instances.
|
|
15
|
+
|
|
16
|
+
#### Scenario: Single dimension matrix
|
|
17
|
+
|
|
18
|
+
- **WHEN** a task defines a single dimension matrix (e.g., `matrix: { env: ['dev', 'prod'] }`)
|
|
19
|
+
- **THEN** the engine SHALL generate independent task instances for each value.
|
|
20
|
+
|
|
21
|
+
#### Scenario: Multi-dimensional matrix
|
|
22
|
+
|
|
23
|
+
- **WHEN** a task defines a multi-dimensional matrix (e.g., `matrix: { os: ['linux', 'windows'], node: [18, 20] }`)
|
|
24
|
+
- **THEN** the engine SHALL generate independent task instances for every combination (Cartesian product) of the matrix dimensions.
|
|
25
|
+
|
|
26
|
+
### Requirement: Contextual Matrix Variables
|
|
27
|
+
|
|
28
|
+
The `TaskRunner` SHALL provide the matrix permutation values to the execution context of the generated child tasks.
|
|
29
|
+
|
|
30
|
+
#### Scenario: Accessing matrix variables
|
|
31
|
+
|
|
32
|
+
- **WHEN** a child matrix task is executed for a permutation like `{ os: 'linux', node: 18 }`
|
|
33
|
+
- **THEN** its `run` function SHALL be able to access the permutation values via the context (e.g., `context.matrix.os` would be `'linux'` and `context.matrix.node` would be `18`).
|
|
34
|
+
|
|
35
|
+
### Requirement: Matrix Dependency Resolution
|
|
36
|
+
|
|
37
|
+
The `TaskRunner` SHALL correctly resolve dependencies involving matrix tasks.
|
|
38
|
+
|
|
39
|
+
#### Scenario: Depending on a matrix task
|
|
40
|
+
|
|
41
|
+
- **WHEN** a standard task depends on a matrix task
|
|
42
|
+
- **THEN** the standard task SHALL wait for all child tasks of the matrix to complete successfully before executing.
|
|
43
|
+
|
|
44
|
+
#### Scenario: Matrix task dependencies
|
|
45
|
+
|
|
46
|
+
- **WHEN** a matrix task depends on another task
|
|
47
|
+
- **THEN** all generated child tasks of the matrix SHALL wait for the parent task to complete before executing.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
## 1. Implementation
|
|
2
|
+
|
|
3
|
+
- [ ] 1.1 Update `TaskStep.ts` interface to include an optional `matrix` property (e.g., `Record<string, unknown[]>`).
|
|
4
|
+
- [ ] 1.2 Update the `TaskRunnerBuilder` and `TaskGraph` validation to accept and validate the `matrix` property.
|
|
5
|
+
- [ ] 1.3 Implement a matrix permutation generator utility to compute Cartesian products of matrix dimensions.
|
|
6
|
+
- [ ] 1.4 Update `TaskStateManager.ts` or `TaskGraph.ts` to dynamically expand matrix steps into individual nodes during initialization, resolving dependencies appropriately.
|
|
7
|
+
- [ ] 1.5 Update the task execution context to provide the current matrix permutation variables to the `run` function.
|
|
8
|
+
- [ ] 1.6 Update the Mermaid Graph utility in `TaskRunner.ts` to correctly output nodes and edges for the dynamically generated matrix steps.
|
|
9
|
+
- [ ] 1.7 Add unit tests covering matrix generation, execution, failure handling, and context passing.
|
|
10
|
+
- [ ] 1.8 Add benchmarks for graph generation and state initialization with large matrix sets to ensure performance targets are met.
|
|
11
|
+
- [ ] 1.9 Add user-facing documentation for the `matrix` feature, including examples.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Change: Workflow Observability and Logging Enhancements
|
|
2
|
+
|
|
3
|
+
## Why
|
|
4
|
+
|
|
5
|
+
As workflows scale in complexity and run in diverse environments (CI/CD, containerized workers, local development), tracking their execution becomes difficult. Currently, users have to manually hook into the `EventBus` to build their own loggers or metrics exporters. Providing built-in, standardized observability tools (like a structured logger or CLI output formatter) will significantly improve the Developer Experience (DX) and reduce boilerplate for debugging complex task graphs.
|
|
6
|
+
|
|
7
|
+
## What Changes
|
|
8
|
+
|
|
9
|
+
- Introduce a standardized `LoggerPlugin` (or similar logging abstraction) that hooks into the existing `EventBus`.
|
|
10
|
+
- Support multiple output formats: `text` (human-readable for CLI) and `json` (structured logging for ingestion by tools like Datadog, ELK, CloudWatch).
|
|
11
|
+
- Capture and format key metrics automatically: task duration, error stack traces, and workflow summaries.
|
|
12
|
+
|
|
13
|
+
## Impact
|
|
14
|
+
|
|
15
|
+
- Affected specs: `task-runner` (adding observability capabilities)
|
|
16
|
+
- Affected code: `src/plugins/LoggerPlugin.ts` (new), `TaskRunnerBuilder` (optional `.withLogger()` integration).
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
## ADDED Requirements
|
|
2
|
+
|
|
3
|
+
### Requirement: Built-in Observability Logging
|
|
4
|
+
|
|
5
|
+
The system SHALL provide a standardized mechanism to log workflow and task execution events in both human-readable and structured formats.
|
|
6
|
+
|
|
7
|
+
#### Scenario: CLI Text Logging
|
|
8
|
+
- **WHEN** a workflow is executed with the text logger enabled
|
|
9
|
+
- **THEN** it SHALL output human-readable lifecycle events (start, success, failure, skip) to the console.
|
|
10
|
+
|
|
11
|
+
#### Scenario: Structured JSON Logging
|
|
12
|
+
- **WHEN** a workflow is executed with the JSON logger enabled
|
|
13
|
+
- **THEN** it SHALL output structured JSON objects for each lifecycle event, suitable for machine ingestion.
|
|
14
|
+
- **AND** the JSON object SHALL contain task name, timestamp, duration, and status.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
## 1. Implementation
|
|
2
|
+
|
|
3
|
+
- [ ] 1.1 Create `LoggerPlugin` implementing the `Plugin` interface.
|
|
4
|
+
- [ ] 1.2 Implement human-readable text formatting for CLI.
|
|
5
|
+
- [ ] 1.3 Implement structured JSON formatting for log ingestion.
|
|
6
|
+
- [ ] 1.4 Add a fluent method to `TaskRunnerBuilder` (e.g., `.withLogger()`) to easily attach the plugin.
|
|
7
|
+
- [ ] 1.5 Write unit tests for the logging outputs and integration.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@calmo/task-runner",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.3.0",
|
|
4
4
|
"description": "A lightweight, type-safe, and domain-agnostic task orchestration engine. It resolves a Directed Acyclic Graph (DAG) of steps, executes independent tasks in parallel, and manages a shared context across the pipeline.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
package/src/EventBus.ts
CHANGED
|
@@ -40,10 +40,9 @@ export class EventBus<TContext> {
|
|
|
40
40
|
event: K,
|
|
41
41
|
callback: RunnerEventListener<TContext, K>
|
|
42
42
|
): void {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
);
|
|
43
|
+
const listeners = this.listeners[event];
|
|
44
|
+
if (listeners) {
|
|
45
|
+
listeners.delete(callback);
|
|
47
46
|
}
|
|
48
47
|
}
|
|
49
48
|
|
|
@@ -56,9 +55,7 @@ export class EventBus<TContext> {
|
|
|
56
55
|
event: K,
|
|
57
56
|
data: RunnerEventPayloads<TContext>[K]
|
|
58
57
|
): void {
|
|
59
|
-
const listeners = this.listeners[event]
|
|
60
|
-
| Set<RunnerEventListener<TContext, K>>
|
|
61
|
-
| undefined;
|
|
58
|
+
const listeners = this.listeners[event];
|
|
62
59
|
if (listeners) {
|
|
63
60
|
for (const listener of listeners) {
|
|
64
61
|
// We use queueMicrotask() to schedule the listener on the microtask queue,
|
package/src/PluginManager.ts
CHANGED
|
@@ -24,12 +24,14 @@ export class PluginManager<TContext> {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
|
-
* Initializes all registered plugins.
|
|
27
|
+
* Initializes all registered plugins in parallel.
|
|
28
28
|
*/
|
|
29
29
|
public async initialize(): Promise<void> {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
const installPromises = this.plugins
|
|
31
|
+
.map((plugin) => plugin.install(this.context))
|
|
32
|
+
.filter((result): result is Promise<void> => result instanceof Promise);
|
|
33
|
+
|
|
34
|
+
await Promise.all(installPromises);
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
/**
|
package/src/TaskRunner.ts
CHANGED
|
@@ -88,7 +88,8 @@ export class TaskRunner<TContext> {
|
|
|
88
88
|
* @returns A string containing the Mermaid graph definition.
|
|
89
89
|
*/
|
|
90
90
|
public static getMermaidGraph<T>(steps: TaskStep<T>[]): string {
|
|
91
|
-
const
|
|
91
|
+
const nodeLines: string[] = ["graph TD"];
|
|
92
|
+
const edgeLines = new Set<string>();
|
|
92
93
|
const idMap = new Map<string, string>();
|
|
93
94
|
const usedIds = new Set<string>();
|
|
94
95
|
const baseIdCounters = new Map<string, number>();
|
|
@@ -124,28 +125,31 @@ export class TaskRunner<TContext> {
|
|
|
124
125
|
return uniqueId;
|
|
125
126
|
};
|
|
126
127
|
|
|
127
|
-
//
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
128
|
+
// Process nodes and edges in a single pass over input steps
|
|
129
|
+
const processedNodes = new Set<string>();
|
|
130
|
+
for (let i = 0; i < steps.length; i++) {
|
|
131
|
+
const step = steps[i];
|
|
132
|
+
const name = step.name;
|
|
133
|
+
const stepId = getUniqueId(name);
|
|
134
|
+
|
|
135
|
+
const sizeBefore = processedNodes.size;
|
|
136
|
+
processedNodes.add(stepId);
|
|
137
|
+
|
|
138
|
+
if (processedNodes.size !== sizeBefore) {
|
|
139
|
+
const escapedName = name.replaceAll("\"", """);
|
|
140
|
+
nodeLines.push(` ${stepId}["${escapedName}"]`);
|
|
135
141
|
}
|
|
136
|
-
}
|
|
137
142
|
|
|
138
|
-
for (const step of steps) {
|
|
139
143
|
if (step.dependencies) {
|
|
140
|
-
const
|
|
141
|
-
for (
|
|
142
|
-
const depId = getUniqueId(
|
|
143
|
-
|
|
144
|
+
const deps = step.dependencies;
|
|
145
|
+
for (let j = 0; j < deps.length; j++) {
|
|
146
|
+
const depId = getUniqueId(deps[j]);
|
|
147
|
+
edgeLines.add(` ${depId} --> ${stepId}`);
|
|
144
148
|
}
|
|
145
149
|
}
|
|
146
150
|
}
|
|
147
151
|
|
|
148
|
-
return [...
|
|
152
|
+
return nodeLines.concat([...edgeLines]).join("\n");
|
|
149
153
|
}
|
|
150
154
|
|
|
151
155
|
/**
|
package/src/TaskStateManager.ts
CHANGED
|
@@ -15,6 +15,7 @@ export class TaskStateManager<TContext> {
|
|
|
15
15
|
private dependencyGraph = new Map<string, TaskStep<TContext>[]>();
|
|
16
16
|
private dependencyCounts = new Map<string, number>();
|
|
17
17
|
private readyQueue: TaskStep<TContext>[] = [];
|
|
18
|
+
private taskDefinitions = new Map<string, TaskStep<TContext>>();
|
|
18
19
|
|
|
19
20
|
constructor(private eventBus: EventBus<TContext>) {}
|
|
20
21
|
|
|
@@ -30,8 +31,10 @@ export class TaskStateManager<TContext> {
|
|
|
30
31
|
|
|
31
32
|
this.dependencyGraph.clear();
|
|
32
33
|
this.dependencyCounts.clear();
|
|
34
|
+
this.taskDefinitions.clear();
|
|
33
35
|
|
|
34
36
|
for (const step of steps) {
|
|
37
|
+
this.taskDefinitions.set(step.name, step);
|
|
35
38
|
const deps = step.dependencies ?? [];
|
|
36
39
|
this.dependencyCounts.set(step.name, deps.length);
|
|
37
40
|
|
|
@@ -56,7 +59,7 @@ export class TaskStateManager<TContext> {
|
|
|
56
59
|
* @returns An array of tasks that are ready to run.
|
|
57
60
|
*/
|
|
58
61
|
processDependencies(): TaskStep<TContext>[] {
|
|
59
|
-
const toRun =
|
|
62
|
+
const toRun = this.readyQueue;
|
|
60
63
|
this.readyQueue = [];
|
|
61
64
|
|
|
62
65
|
// Remove them from pendingSteps as they are now handed off to the executor
|
|
@@ -89,6 +92,13 @@ export class TaskStateManager<TContext> {
|
|
|
89
92
|
|
|
90
93
|
if (result.status === "success") {
|
|
91
94
|
this.handleSuccess(step.name);
|
|
95
|
+
} else if (result.status === "failure") {
|
|
96
|
+
// If continueOnError is true, treat as success for dependents to unblock the workflow
|
|
97
|
+
if (this.taskDefinitions.get(step.name)?.continueOnError) {
|
|
98
|
+
this.handleSuccess(step.name);
|
|
99
|
+
} else {
|
|
100
|
+
this.cascadeFailure(step.name);
|
|
101
|
+
}
|
|
92
102
|
} else {
|
|
93
103
|
this.cascadeFailure(step.name);
|
|
94
104
|
}
|
package/src/TaskStep.ts
CHANGED
|
@@ -25,6 +25,20 @@ export interface TaskStep<TContext> {
|
|
|
25
25
|
*/
|
|
26
26
|
priority?: number;
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Optional flag to indicate that the workflow should continue even if this task fails.
|
|
30
|
+
* If true, dependent tasks will execute as if this task succeeded.
|
|
31
|
+
* The task result will still be marked as "failure".
|
|
32
|
+
* Default is false.
|
|
33
|
+
*/
|
|
34
|
+
continueOnError?: boolean;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Optional maximum execution time in milliseconds.
|
|
38
|
+
* If the task runs longer than this, it will be cancelled and marked as failed.
|
|
39
|
+
*/
|
|
40
|
+
timeout?: number;
|
|
41
|
+
|
|
28
42
|
/**
|
|
29
43
|
* The core logic of the task.
|
|
30
44
|
* @param context The shared context object, allowing for state to be passed between tasks.
|
|
@@ -22,9 +22,9 @@ export class DryRunExecutionStrategy<
|
|
|
22
22
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
23
23
|
_signal?: AbortSignal
|
|
24
24
|
): Promise<TaskResult> {
|
|
25
|
-
return
|
|
25
|
+
return {
|
|
26
26
|
status: "success",
|
|
27
27
|
message: "Dry run: simulated success " + step.name,
|
|
28
|
-
}
|
|
28
|
+
};
|
|
29
29
|
}
|
|
30
30
|
}
|
|
@@ -13,8 +13,51 @@ export class StandardExecutionStrategy<
|
|
|
13
13
|
context: TContext,
|
|
14
14
|
signal?: AbortSignal
|
|
15
15
|
): Promise<TaskResult> {
|
|
16
|
+
if (!step.timeout) {
|
|
17
|
+
try {
|
|
18
|
+
return await step.run(context, signal);
|
|
19
|
+
} catch (e) {
|
|
20
|
+
// Check if error is due to abort
|
|
21
|
+
if (
|
|
22
|
+
signal?.aborted &&
|
|
23
|
+
((e instanceof Error && e.name === "AbortError") ||
|
|
24
|
+
signal.reason === e)
|
|
25
|
+
) {
|
|
26
|
+
return {
|
|
27
|
+
status: "cancelled",
|
|
28
|
+
message: "Task cancelled during execution",
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
status: "failure",
|
|
33
|
+
error: e instanceof Error ? e.message : String(e),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const abortController = new AbortController();
|
|
39
|
+
const timeoutSignal = signal
|
|
40
|
+
? AbortSignal.any([signal, abortController.signal])
|
|
41
|
+
: abortController.signal;
|
|
42
|
+
|
|
43
|
+
let timer: NodeJS.Timeout | undefined;
|
|
44
|
+
let resolveTimeout!: (value: TaskResult) => void;
|
|
45
|
+
|
|
16
46
|
try {
|
|
17
|
-
|
|
47
|
+
const timeoutPromise = new Promise<TaskResult>((resolve) => {
|
|
48
|
+
resolveTimeout = resolve;
|
|
49
|
+
timer = setTimeout(() => {
|
|
50
|
+
abortController.abort(new Error("Timeout"));
|
|
51
|
+
resolve({
|
|
52
|
+
status: "failure",
|
|
53
|
+
error: `Task timed out after ${step.timeout}ms`,
|
|
54
|
+
});
|
|
55
|
+
}, step.timeout);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const taskPromise = step.run(context, timeoutSignal);
|
|
59
|
+
|
|
60
|
+
return await Promise.race([taskPromise, timeoutPromise]);
|
|
18
61
|
} catch (e) {
|
|
19
62
|
// Check if error is due to abort
|
|
20
63
|
if (
|
|
@@ -30,6 +73,10 @@ export class StandardExecutionStrategy<
|
|
|
30
73
|
status: "failure",
|
|
31
74
|
error: e instanceof Error ? e.message : String(e),
|
|
32
75
|
};
|
|
76
|
+
} finally {
|
|
77
|
+
clearTimeout(timer);
|
|
78
|
+
// Settle the timeout promise to avoid memory leaks from Promise.race
|
|
79
|
+
resolveTimeout({ status: "cancelled" } as TaskResult);
|
|
33
80
|
}
|
|
34
81
|
}
|
|
35
82
|
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|