@calmo/task-runner 1.0.1 → 1.1.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/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## 1.1.0 (2026-01-18)
2
+
3
+ * Merge branch 'main' into feat/task-runner-observer-pattern-15088823676344229252 ([d38d6f8](https://github.com/thalesraymond/task-runner/commit/d38d6f8))
4
+ * Merge branch 'main' into feat/task-runner-observer-pattern-15088823676344229252 ([2638801](https://github.com/thalesraymond/task-runner/commit/2638801))
5
+ * Merge pull request #22 from thalesraymond/feat/task-runner-observer-pattern-15088823676344229252 ([06e0049](https://github.com/thalesraymond/task-runner/commit/06e0049)), closes [#22](https://github.com/thalesraymond/task-runner/issues/22)
6
+ * fix: add file extensions and types to test file to fix CI errors ([54b186f](https://github.com/thalesraymond/task-runner/commit/54b186f))
7
+ * fix: remove any type usage in TaskRunner to satisfy linter ([b0a21a7](https://github.com/thalesraymond/task-runner/commit/b0a21a7))
8
+ * fix: revert unauthorized changes to package.json and tsconfig.test.json ([cd44f9e](https://github.com/thalesraymond/task-runner/commit/cd44f9e))
9
+ * test: 💍 force 100% coverage ([2e489f0](https://github.com/thalesraymond/task-runner/commit/2e489f0))
10
+ * test: increase coverage to 100% ([5cd1a44](https://github.com/thalesraymond/task-runner/commit/5cd1a44))
11
+ * feat: implement Observer Pattern event system in TaskRunner ([d682983](https://github.com/thalesraymond/task-runner/commit/d682983))
12
+ * feat: implement Observer Pattern event system in TaskRunner ([48bd3d0](https://github.com/thalesraymond/task-runner/commit/48bd3d0))
13
+ * chore: add typescript check for tests ([9b6c5d4](https://github.com/thalesraymond/task-runner/commit/9b6c5d4))
14
+
1
15
  ## <small>1.0.1 (2026-01-17)</small>
2
16
 
3
17
  * fix: 🐛 fixing commit script and adding a fix to test auto relea ([97cedf4](https://github.com/thalesraymond/task-runner/commit/97cedf4))
@@ -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'>32/32</span>
28
+ <span class='fraction'>51/51</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'>21/21</span>
35
+ <span class='fraction'>27/27</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'>9/9</span>
42
+ <span class='fraction'>12/12</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'>29/29</span>
49
+ <span class='fraction'>48/48</span>
50
50
  </div>
51
51
 
52
52
 
@@ -149,7 +149,133 @@
149
149
  <a name='L84'></a><a href='#L84'>84</a>
150
150
  <a name='L85'></a><a href='#L85'>85</a>
151
151
  <a name='L86'></a><a href='#L86'>86</a>
152
- <a name='L87'></a><a href='#L87'>87</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
152
+ <a name='L87'></a><a href='#L87'>87</a>
153
+ <a name='L88'></a><a href='#L88'>88</a>
154
+ <a name='L89'></a><a href='#L89'>89</a>
155
+ <a name='L90'></a><a href='#L90'>90</a>
156
+ <a name='L91'></a><a href='#L91'>91</a>
157
+ <a name='L92'></a><a href='#L92'>92</a>
158
+ <a name='L93'></a><a href='#L93'>93</a>
159
+ <a name='L94'></a><a href='#L94'>94</a>
160
+ <a name='L95'></a><a href='#L95'>95</a>
161
+ <a name='L96'></a><a href='#L96'>96</a>
162
+ <a name='L97'></a><a href='#L97'>97</a>
163
+ <a name='L98'></a><a href='#L98'>98</a>
164
+ <a name='L99'></a><a href='#L99'>99</a>
165
+ <a name='L100'></a><a href='#L100'>100</a>
166
+ <a name='L101'></a><a href='#L101'>101</a>
167
+ <a name='L102'></a><a href='#L102'>102</a>
168
+ <a name='L103'></a><a href='#L103'>103</a>
169
+ <a name='L104'></a><a href='#L104'>104</a>
170
+ <a name='L105'></a><a href='#L105'>105</a>
171
+ <a name='L106'></a><a href='#L106'>106</a>
172
+ <a name='L107'></a><a href='#L107'>107</a>
173
+ <a name='L108'></a><a href='#L108'>108</a>
174
+ <a name='L109'></a><a href='#L109'>109</a>
175
+ <a name='L110'></a><a href='#L110'>110</a>
176
+ <a name='L111'></a><a href='#L111'>111</a>
177
+ <a name='L112'></a><a href='#L112'>112</a>
178
+ <a name='L113'></a><a href='#L113'>113</a>
179
+ <a name='L114'></a><a href='#L114'>114</a>
180
+ <a name='L115'></a><a href='#L115'>115</a>
181
+ <a name='L116'></a><a href='#L116'>116</a>
182
+ <a name='L117'></a><a href='#L117'>117</a>
183
+ <a name='L118'></a><a href='#L118'>118</a>
184
+ <a name='L119'></a><a href='#L119'>119</a>
185
+ <a name='L120'></a><a href='#L120'>120</a>
186
+ <a name='L121'></a><a href='#L121'>121</a>
187
+ <a name='L122'></a><a href='#L122'>122</a>
188
+ <a name='L123'></a><a href='#L123'>123</a>
189
+ <a name='L124'></a><a href='#L124'>124</a>
190
+ <a name='L125'></a><a href='#L125'>125</a>
191
+ <a name='L126'></a><a href='#L126'>126</a>
192
+ <a name='L127'></a><a href='#L127'>127</a>
193
+ <a name='L128'></a><a href='#L128'>128</a>
194
+ <a name='L129'></a><a href='#L129'>129</a>
195
+ <a name='L130'></a><a href='#L130'>130</a>
196
+ <a name='L131'></a><a href='#L131'>131</a>
197
+ <a name='L132'></a><a href='#L132'>132</a>
198
+ <a name='L133'></a><a href='#L133'>133</a>
199
+ <a name='L134'></a><a href='#L134'>134</a>
200
+ <a name='L135'></a><a href='#L135'>135</a>
201
+ <a name='L136'></a><a href='#L136'>136</a>
202
+ <a name='L137'></a><a href='#L137'>137</a>
203
+ <a name='L138'></a><a href='#L138'>138</a>
204
+ <a name='L139'></a><a href='#L139'>139</a>
205
+ <a name='L140'></a><a href='#L140'>140</a>
206
+ <a name='L141'></a><a href='#L141'>141</a>
207
+ <a name='L142'></a><a href='#L142'>142</a>
208
+ <a name='L143'></a><a href='#L143'>143</a>
209
+ <a name='L144'></a><a href='#L144'>144</a>
210
+ <a name='L145'></a><a href='#L145'>145</a>
211
+ <a name='L146'></a><a href='#L146'>146</a>
212
+ <a name='L147'></a><a href='#L147'>147</a>
213
+ <a name='L148'></a><a href='#L148'>148</a>
214
+ <a name='L149'></a><a href='#L149'>149</a>
215
+ <a name='L150'></a><a href='#L150'>150</a>
216
+ <a name='L151'></a><a href='#L151'>151</a>
217
+ <a name='L152'></a><a href='#L152'>152</a>
218
+ <a name='L153'></a><a href='#L153'>153</a>
219
+ <a name='L154'></a><a href='#L154'>154</a>
220
+ <a name='L155'></a><a href='#L155'>155</a>
221
+ <a name='L156'></a><a href='#L156'>156</a>
222
+ <a name='L157'></a><a href='#L157'>157</a>
223
+ <a name='L158'></a><a href='#L158'>158</a>
224
+ <a name='L159'></a><a href='#L159'>159</a>
225
+ <a name='L160'></a><a href='#L160'>160</a>
226
+ <a name='L161'></a><a href='#L161'>161</a>
227
+ <a name='L162'></a><a href='#L162'>162</a>
228
+ <a name='L163'></a><a href='#L163'>163</a>
229
+ <a name='L164'></a><a href='#L164'>164</a>
230
+ <a name='L165'></a><a href='#L165'>165</a>
231
+ <a name='L166'></a><a href='#L166'>166</a>
232
+ <a name='L167'></a><a href='#L167'>167</a>
233
+ <a name='L168'></a><a href='#L168'>168</a>
234
+ <a name='L169'></a><a href='#L169'>169</a>
235
+ <a name='L170'></a><a href='#L170'>170</a>
236
+ <a name='L171'></a><a href='#L171'>171</a>
237
+ <a name='L172'></a><a href='#L172'>172</a>
238
+ <a name='L173'></a><a href='#L173'>173</a>
239
+ <a name='L174'></a><a href='#L174'>174</a>
240
+ <a name='L175'></a><a href='#L175'>175</a>
241
+ <a name='L176'></a><a href='#L176'>176</a>
242
+ <a name='L177'></a><a href='#L177'>177</a>
243
+ <a name='L178'></a><a href='#L178'>178</a>
244
+ <a name='L179'></a><a href='#L179'>179</a>
245
+ <a name='L180'></a><a href='#L180'>180</a>
246
+ <a name='L181'></a><a href='#L181'>181</a>
247
+ <a name='L182'></a><a href='#L182'>182</a>
248
+ <a name='L183'></a><a href='#L183'>183</a>
249
+ <a name='L184'></a><a href='#L184'>184</a>
250
+ <a name='L185'></a><a href='#L185'>185</a>
251
+ <a name='L186'></a><a href='#L186'>186</a>
252
+ <a name='L187'></a><a href='#L187'>187</a>
253
+ <a name='L188'></a><a href='#L188'>188</a>
254
+ <a name='L189'></a><a href='#L189'>189</a>
255
+ <a name='L190'></a><a href='#L190'>190</a>
256
+ <a name='L191'></a><a href='#L191'>191</a>
257
+ <a name='L192'></a><a href='#L192'>192</a>
258
+ <a name='L193'></a><a href='#L193'>193</a>
259
+ <a name='L194'></a><a href='#L194'>194</a>
260
+ <a name='L195'></a><a href='#L195'>195</a>
261
+ <a name='L196'></a><a href='#L196'>196</a>
262
+ <a name='L197'></a><a href='#L197'>197</a>
263
+ <a name='L198'></a><a href='#L198'>198</a>
264
+ <a name='L199'></a><a href='#L199'>199</a>
265
+ <a name='L200'></a><a href='#L200'>200</a>
266
+ <a name='L201'></a><a href='#L201'>201</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
267
+ <span class="cline-any cline-neutral">&nbsp;</span>
268
+ <span class="cline-any cline-neutral">&nbsp;</span>
269
+ <span class="cline-any cline-neutral">&nbsp;</span>
270
+ <span class="cline-any cline-neutral">&nbsp;</span>
271
+ <span class="cline-any cline-neutral">&nbsp;</span>
272
+ <span class="cline-any cline-neutral">&nbsp;</span>
273
+ <span class="cline-any cline-neutral">&nbsp;</span>
274
+ <span class="cline-any cline-neutral">&nbsp;</span>
275
+ <span class="cline-any cline-neutral">&nbsp;</span>
276
+ <span class="cline-any cline-neutral">&nbsp;</span>
277
+ <span class="cline-any cline-neutral">&nbsp;</span>
278
+ <span class="cline-any cline-neutral">&nbsp;</span>
153
279
  <span class="cline-any cline-neutral">&nbsp;</span>
154
280
  <span class="cline-any cline-neutral">&nbsp;</span>
155
281
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -158,12 +284,10 @@
158
284
  <span class="cline-any cline-neutral">&nbsp;</span>
159
285
  <span class="cline-any cline-neutral">&nbsp;</span>
160
286
  <span class="cline-any cline-neutral">&nbsp;</span>
161
- <span class="cline-any cline-yes">12x</span>
162
287
  <span class="cline-any cline-neutral">&nbsp;</span>
163
288
  <span class="cline-any cline-neutral">&nbsp;</span>
164
289
  <span class="cline-any cline-neutral">&nbsp;</span>
165
290
  <span class="cline-any cline-neutral">&nbsp;</span>
166
- <span class="cline-any cline-yes">12x</span>
167
291
  <span class="cline-any cline-neutral">&nbsp;</span>
168
292
  <span class="cline-any cline-neutral">&nbsp;</span>
169
293
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -173,36 +297,136 @@
173
297
  <span class="cline-any cline-neutral">&nbsp;</span>
174
298
  <span class="cline-any cline-neutral">&nbsp;</span>
175
299
  <span class="cline-any cline-neutral">&nbsp;</span>
176
- <span class="cline-any cline-yes">12x</span>
177
300
  <span class="cline-any cline-neutral">&nbsp;</span>
178
- <span class="cline-any cline-yes">12x</span>
179
- <span class="cline-any cline-yes">19x</span>
180
- <span class="cline-any cline-yes">69x</span>
181
301
  <span class="cline-any cline-neutral">&nbsp;</span>
182
302
  <span class="cline-any cline-neutral">&nbsp;</span>
303
+ <span class="cline-any cline-neutral">&nbsp;</span>
304
+ <span class="cline-any cline-neutral">&nbsp;</span>
305
+ <span class="cline-any cline-neutral">&nbsp;</span>
306
+ <span class="cline-any cline-neutral">&nbsp;</span>
307
+ <span class="cline-any cline-neutral">&nbsp;</span>
308
+ <span class="cline-any cline-neutral">&nbsp;</span>
309
+ <span class="cline-any cline-neutral">&nbsp;</span>
310
+ <span class="cline-any cline-neutral">&nbsp;</span>
311
+ <span class="cline-any cline-neutral">&nbsp;</span>
312
+ <span class="cline-any cline-neutral">&nbsp;</span>
313
+ <span class="cline-any cline-neutral">&nbsp;</span>
314
+ <span class="cline-any cline-neutral">&nbsp;</span>
315
+ <span class="cline-any cline-neutral">&nbsp;</span>
316
+ <span class="cline-any cline-neutral">&nbsp;</span>
317
+ <span class="cline-any cline-yes">19x</span>
183
318
  <span class="cline-any cline-yes">19x</span>
184
- <span class="cline-any cline-yes">48x</span>
185
- <span class="cline-any cline-yes">48x</span>
186
- <span class="cline-any cline-yes">38x</span>
187
319
  <span class="cline-any cline-neutral">&nbsp;</span>
188
320
  <span class="cline-any cline-neutral">&nbsp;</span>
189
321
  <span class="cline-any cline-neutral">&nbsp;</span>
190
322
  <span class="cline-any cline-neutral">&nbsp;</span>
191
323
  <span class="cline-any cline-yes">19x</span>
192
- <span class="cline-any cline-yes">48x</span>
193
- <span class="cline-any cline-yes">47x</span>
194
- <span class="cline-any cline-yes">48x</span>
195
- <span class="cline-any cline-yes">41x</span>
196
324
  <span class="cline-any cline-neutral">&nbsp;</span>
197
- <span class="cline-any cline-yes">48x</span>
198
- <span class="cline-any cline-yes">6x</span>
199
325
  <span class="cline-any cline-neutral">&nbsp;</span>
200
326
  <span class="cline-any cline-neutral">&nbsp;</span>
201
327
  <span class="cline-any cline-neutral">&nbsp;</span>
202
328
  <span class="cline-any cline-neutral">&nbsp;</span>
203
329
  <span class="cline-any cline-neutral">&nbsp;</span>
204
330
  <span class="cline-any cline-neutral">&nbsp;</span>
205
- <span class="cline-any cline-yes">19x</span>
331
+ <span class="cline-any cline-neutral">&nbsp;</span>
332
+ <span class="cline-any cline-neutral">&nbsp;</span>
333
+ <span class="cline-any cline-neutral">&nbsp;</span>
334
+ <span class="cline-any cline-yes">12x</span>
335
+ <span class="cline-any cline-neutral">&nbsp;</span>
336
+ <span class="cline-any cline-neutral">&nbsp;</span>
337
+ <span class="cline-any cline-yes">11x</span>
338
+ <span class="cline-any cline-neutral">&nbsp;</span>
339
+ <span class="cline-any cline-neutral">&nbsp;</span>
340
+ <span class="cline-any cline-yes">12x</span>
341
+ <span class="cline-any cline-neutral">&nbsp;</span>
342
+ <span class="cline-any cline-neutral">&nbsp;</span>
343
+ <span class="cline-any cline-neutral">&nbsp;</span>
344
+ <span class="cline-any cline-neutral">&nbsp;</span>
345
+ <span class="cline-any cline-neutral">&nbsp;</span>
346
+ <span class="cline-any cline-neutral">&nbsp;</span>
347
+ <span class="cline-any cline-neutral">&nbsp;</span>
348
+ <span class="cline-any cline-neutral">&nbsp;</span>
349
+ <span class="cline-any cline-neutral">&nbsp;</span>
350
+ <span class="cline-any cline-neutral">&nbsp;</span>
351
+ <span class="cline-any cline-neutral">&nbsp;</span>
352
+ <span class="cline-any cline-neutral">&nbsp;</span>
353
+ <span class="cline-any cline-neutral">&nbsp;</span>
354
+ <span class="cline-any cline-yes">2x</span>
355
+ <span class="cline-any cline-yes">1x</span>
356
+ <span class="cline-any cline-neutral">&nbsp;</span>
357
+ <span class="cline-any cline-neutral">&nbsp;</span>
358
+ <span class="cline-any cline-neutral">&nbsp;</span>
359
+ <span class="cline-any cline-neutral">&nbsp;</span>
360
+ <span class="cline-any cline-neutral">&nbsp;</span>
361
+ <span class="cline-any cline-neutral">&nbsp;</span>
362
+ <span class="cline-any cline-neutral">&nbsp;</span>
363
+ <span class="cline-any cline-neutral">&nbsp;</span>
364
+ <span class="cline-any cline-neutral">&nbsp;</span>
365
+ <span class="cline-any cline-neutral">&nbsp;</span>
366
+ <span class="cline-any cline-neutral">&nbsp;</span>
367
+ <span class="cline-any cline-neutral">&nbsp;</span>
368
+ <span class="cline-any cline-neutral">&nbsp;</span>
369
+ <span class="cline-any cline-neutral">&nbsp;</span>
370
+ <span class="cline-any cline-yes">94x</span>
371
+ <span class="cline-any cline-neutral">&nbsp;</span>
372
+ <span class="cline-any cline-neutral">&nbsp;</span>
373
+ <span class="cline-any cline-yes">94x</span>
374
+ <span class="cline-any cline-yes">13x</span>
375
+ <span class="cline-any cline-yes">13x</span>
376
+ <span class="cline-any cline-yes">13x</span>
377
+ <span class="cline-any cline-neutral">&nbsp;</span>
378
+ <span class="cline-any cline-neutral">&nbsp;</span>
379
+ <span class="cline-any cline-yes">1x</span>
380
+ <span class="cline-any cline-neutral">&nbsp;</span>
381
+ <span class="cline-any cline-neutral">&nbsp;</span>
382
+ <span class="cline-any cline-neutral">&nbsp;</span>
383
+ <span class="cline-any cline-neutral">&nbsp;</span>
384
+ <span class="cline-any cline-neutral">&nbsp;</span>
385
+ <span class="cline-any cline-neutral">&nbsp;</span>
386
+ <span class="cline-any cline-neutral">&nbsp;</span>
387
+ <span class="cline-any cline-neutral">&nbsp;</span>
388
+ <span class="cline-any cline-neutral">&nbsp;</span>
389
+ <span class="cline-any cline-neutral">&nbsp;</span>
390
+ <span class="cline-any cline-neutral">&nbsp;</span>
391
+ <span class="cline-any cline-neutral">&nbsp;</span>
392
+ <span class="cline-any cline-neutral">&nbsp;</span>
393
+ <span class="cline-any cline-neutral">&nbsp;</span>
394
+ <span class="cline-any cline-neutral">&nbsp;</span>
395
+ <span class="cline-any cline-neutral">&nbsp;</span>
396
+ <span class="cline-any cline-yes">18x</span>
397
+ <span class="cline-any cline-neutral">&nbsp;</span>
398
+ <span class="cline-any cline-yes">18x</span>
399
+ <span class="cline-any cline-neutral">&nbsp;</span>
400
+ <span class="cline-any cline-yes">18x</span>
401
+ <span class="cline-any cline-yes">26x</span>
402
+ <span class="cline-any cline-yes">80x</span>
403
+ <span class="cline-any cline-neutral">&nbsp;</span>
404
+ <span class="cline-any cline-neutral">&nbsp;</span>
405
+ <span class="cline-any cline-yes">26x</span>
406
+ <span class="cline-any cline-yes">57x</span>
407
+ <span class="cline-any cline-yes">57x</span>
408
+ <span class="cline-any cline-yes">42x</span>
409
+ <span class="cline-any cline-neutral">&nbsp;</span>
410
+ <span class="cline-any cline-neutral">&nbsp;</span>
411
+ <span class="cline-any cline-neutral">&nbsp;</span>
412
+ <span class="cline-any cline-neutral">&nbsp;</span>
413
+ <span class="cline-any cline-yes">26x</span>
414
+ <span class="cline-any cline-yes">57x</span>
415
+ <span class="cline-any cline-yes">56x</span>
416
+ <span class="cline-any cline-yes">57x</span>
417
+ <span class="cline-any cline-yes">45x</span>
418
+ <span class="cline-any cline-neutral">&nbsp;</span>
419
+ <span class="cline-any cline-yes">57x</span>
420
+ <span class="cline-any cline-yes">7x</span>
421
+ <span class="cline-any cline-neutral">&nbsp;</span>
422
+ <span class="cline-any cline-neutral">&nbsp;</span>
423
+ <span class="cline-any cline-neutral">&nbsp;</span>
424
+ <span class="cline-any cline-yes">7x</span>
425
+ <span class="cline-any cline-yes">7x</span>
426
+ <span class="cline-any cline-neutral">&nbsp;</span>
427
+ <span class="cline-any cline-neutral">&nbsp;</span>
428
+ <span class="cline-any cline-neutral">&nbsp;</span>
429
+ <span class="cline-any cline-yes">26x</span>
206
430
  <span class="cline-any cline-neutral">&nbsp;</span>
207
431
  <span class="cline-any cline-neutral">&nbsp;</span>
208
432
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -214,30 +438,76 @@
214
438
  <span class="cline-any cline-neutral">&nbsp;</span>
215
439
  <span class="cline-any cline-neutral">&nbsp;</span>
216
440
  <span class="cline-any cline-neutral">&nbsp;</span>
217
- <span class="cline-any cline-yes">16x</span>
441
+ <span class="cline-any cline-yes">23x</span>
218
442
  <span class="cline-any cline-neutral">&nbsp;</span>
219
- <span class="cline-any cline-yes">21x</span>
220
- <span class="cline-any cline-yes">21x</span>
221
- <span class="cline-any cline-yes">21x</span>
222
- <span class="cline-any cline-yes">19x</span>
443
+ <span class="cline-any cline-yes">27x</span>
444
+ <span class="cline-any cline-yes">27x</span>
445
+ <span class="cline-any cline-yes">27x</span>
446
+ <span class="cline-any cline-yes">27x</span>
447
+ <span class="cline-any cline-yes">25x</span>
223
448
  <span class="cline-any cline-neutral">&nbsp;</span>
224
449
  <span class="cline-any cline-yes">2x</span>
225
450
  <span class="cline-any cline-neutral">&nbsp;</span>
226
451
  <span class="cline-any cline-neutral">&nbsp;</span>
227
452
  <span class="cline-any cline-neutral">&nbsp;</span>
228
453
  <span class="cline-any cline-neutral">&nbsp;</span>
229
- <span class="cline-any cline-yes">21x</span>
454
+ <span class="cline-any cline-yes">27x</span>
455
+ <span class="cline-any cline-yes">27x</span>
456
+ <span class="cline-any cline-yes">27x</span>
230
457
  <span class="cline-any cline-neutral">&nbsp;</span>
231
458
  <span class="cline-any cline-neutral">&nbsp;</span>
232
459
  <span class="cline-any cline-neutral">&nbsp;</span>
233
460
  <span class="cline-any cline-neutral">&nbsp;</span>
234
461
  <span class="cline-any cline-neutral">&nbsp;</span>
235
- <span class="cline-any cline-yes">9x</span>
462
+ <span class="cline-any cline-yes">15x</span>
463
+ <span class="cline-any cline-yes">15x</span>
236
464
  <span class="cline-any cline-neutral">&nbsp;</span>
237
465
  <span class="cline-any cline-neutral">&nbsp;</span>
238
466
  <span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">import { TaskStep } from "./TaskStep.js";
239
467
  import { TaskResult } from "./TaskResult.js";
240
468
  &nbsp;
469
+ /**
470
+ * Define the payload for every possible event in the lifecycle.
471
+ */
472
+ export interface RunnerEventPayloads&lt;TContext&gt; {
473
+ workflowStart: {
474
+ context: TContext;
475
+ steps: TaskStep&lt;TContext&gt;[];
476
+ };
477
+ workflowEnd: {
478
+ context: TContext;
479
+ results: Map&lt;string, TaskResult&gt;;
480
+ };
481
+ taskStart: {
482
+ step: TaskStep&lt;TContext&gt;;
483
+ };
484
+ taskEnd: {
485
+ step: TaskStep&lt;TContext&gt;;
486
+ result: TaskResult;
487
+ };
488
+ taskSkipped: {
489
+ step: TaskStep&lt;TContext&gt;;
490
+ result: TaskResult;
491
+ };
492
+ }
493
+ &nbsp;
494
+ /**
495
+ * A generic listener type that maps the event key to its specific payload.
496
+ */
497
+ export type RunnerEventListener&lt;
498
+ TContext,
499
+ K extends keyof RunnerEventPayloads&lt;TContext&gt;,
500
+ &gt; = (data: RunnerEventPayloads&lt;TContext&gt;[K]) =&gt; void | Promise&lt;void&gt;;
501
+ &nbsp;
502
+ /**
503
+ * Helper type for the listeners map to avoid private access issues in generic contexts.
504
+ */
505
+ type ListenerMap&lt;TContext&gt; = {
506
+ [K in keyof RunnerEventPayloads&lt;TContext&gt;]?: Set&lt;
507
+ RunnerEventListener&lt;TContext, K&gt;
508
+ &gt;;
509
+ };
510
+ &nbsp;
241
511
  /**
242
512
  * The main class that orchestrates the execution of a list of tasks
243
513
  * based on their dependencies, with support for parallel execution.
@@ -245,11 +515,75 @@ import { TaskResult } from "./TaskResult.js";
245
515
  */
246
516
  export class TaskRunner&lt;TContext&gt; {
247
517
  private running = new Set&lt;string&gt;();
518
+ private listeners: ListenerMap&lt;TContext&gt; = {};
248
519
  &nbsp;
249
520
  /**
250
521
  * @param context The shared context object to be passed to each task.
251
522
  */
252
523
  constructor(private context: TContext) {}
524
+ &nbsp;
525
+ /**
526
+ * Subscribe to an event.
527
+ * @param event The event name.
528
+ * @param callback The callback to execute when the event is emitted.
529
+ */
530
+ public on&lt;K extends keyof RunnerEventPayloads&lt;TContext&gt;&gt;(
531
+ event: K,
532
+ callback: RunnerEventListener&lt;TContext, K&gt;
533
+ ): void {
534
+ if (!this.listeners[event]) {
535
+ // Type assertion needed because TypeScript cannot verify that the generic K
536
+ // matches the specific key in the mapped type during assignment.
537
+ this.listeners[event] = new Set() as unknown as ListenerMap&lt;TContext&gt;[K];
538
+ }
539
+ // Type assertion needed to tell TS that this specific Set matches the callback type
540
+ (this.listeners[event] as Set&lt;RunnerEventListener&lt;TContext, K&gt;&gt;).add(
541
+ callback
542
+ );
543
+ }
544
+ &nbsp;
545
+ /**
546
+ * Unsubscribe from an event.
547
+ * @param event The event name.
548
+ * @param callback The callback to remove.
549
+ */
550
+ public off&lt;K extends keyof RunnerEventPayloads&lt;TContext&gt;&gt;(
551
+ event: K,
552
+ callback: RunnerEventListener&lt;TContext, K&gt;
553
+ ): void {
554
+ if (this.listeners[event]) {
555
+ (this.listeners[event] as Set&lt;RunnerEventListener&lt;TContext, K&gt;&gt;).delete(
556
+ callback
557
+ );
558
+ }
559
+ }
560
+ &nbsp;
561
+ /**
562
+ * Emit an event to all subscribers.
563
+ * @param event The event name.
564
+ * @param data The payload for the event.
565
+ */
566
+ private emit&lt;K extends keyof RunnerEventPayloads&lt;TContext&gt;&gt;(
567
+ event: K,
568
+ data: RunnerEventPayloads&lt;TContext&gt;[K]
569
+ ): void {
570
+ const listeners = this.listeners[event] as
571
+ | Set&lt;RunnerEventListener&lt;TContext, K&gt;&gt;
572
+ | undefined;
573
+ if (listeners) {
574
+ for (const listener of listeners) {
575
+ try {
576
+ listener(data);
577
+ } catch (error) {
578
+ // Prevent listener errors from bubbling up
579
+ console.error(
580
+ `Error in event listener for ${String(event)}:`,
581
+ error
582
+ );
583
+ }
584
+ }
585
+ }
586
+ }
253
587
  &nbsp;
254
588
  /**
255
589
  * Executes a list of tasks, respecting their dependencies and running
@@ -259,6 +593,8 @@ export class TaskRunner&lt;TContext&gt; {
259
593
  * and values are the corresponding TaskResult objects.
260
594
  */
261
595
  async execute(steps: TaskStep&lt;TContext&gt;[]): Promise&lt;Map&lt;string, TaskResult&gt;&gt; {
596
+ this.emit("workflowStart", { context: this.context, steps });
597
+ &nbsp;
262
598
  const results = new Map&lt;string, TaskResult&gt;();
263
599
  &nbsp;
264
600
  while (results.size &lt; steps.length) {
@@ -281,10 +617,12 @@ export class TaskRunner&lt;TContext&gt; {
281
617
  (dep) =&gt; results.has(dep) &amp;&amp; results.get(dep)?.status !== "success"
282
618
  );
283
619
  if (failedDep) {
284
- results.set(step.name, {
620
+ const result: TaskResult = {
285
621
  status: "skipped",
286
622
  message: `Skipped due to failed dependency: ${failedDep}`,
287
- });
623
+ };
624
+ results.set(step.name, result);
625
+ this.emit("taskSkipped", { step, result });
288
626
  }
289
627
  }
290
628
  &nbsp;
@@ -303,6 +641,7 @@ export class TaskRunner&lt;TContext&gt; {
303
641
  await Promise.all(
304
642
  readySteps.map(async (step) =&gt; {
305
643
  this.running.add(step.name);
644
+ this.emit("taskStart", { step });
306
645
  try {
307
646
  const result = await step.run(this.context);
308
647
  results.set(step.name, result);
@@ -313,11 +652,14 @@ export class TaskRunner&lt;TContext&gt; {
313
652
  });
314
653
  } finally {
315
654
  this.running.delete(step.name);
655
+ const result = results.get(step.name)!;
656
+ this.emit("taskEnd", { step, result });
316
657
  }
317
658
  })
318
659
  );
319
660
  }
320
661
  &nbsp;
662
+ this.emit("workflowEnd", { context: this.context, results });
321
663
  return results;
322
664
  }
323
665
  }
@@ -328,7 +670,7 @@ export class TaskRunner&lt;TContext&gt; {
328
670
  <div class='footer quiet pad2 space-top1 center small'>
329
671
  Code coverage generated by
330
672
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
331
- at 2026-01-17T23:21:19.970Z
673
+ at 2026-01-18T01:05:27.353Z
332
674
  </div>
333
675
  <script src="prettify.js"></script>
334
676
  <script>
@@ -1,2 +1,2 @@
1
- {"/home/runner/work/task-runner/task-runner/src/TaskRunner.ts": {"path":"/home/runner/work/task-runner/task-runner/src/TaskRunner.ts","statementMap":{"0":{"start":{"line":10,"column":20},"end":{"line":10,"column":null}},"1":{"start":{"line":15,"column":22},"end":{"line":15,"column":41}},"2":{"start":{"line":25,"column":20},"end":{"line":25,"column":null}},"3":{"start":{"line":27,"column":4},"end":{"line":82,"column":null}},"4":{"start":{"line":28,"column":27},"end":{"line":30,"column":null}},"5":{"start":{"line":29,"column":18},"end":{"line":29,"column":null}},"6":{"start":{"line":32,"column":25},"end":{"line":37,"column":null}},"7":{"start":{"line":33,"column":21},"end":{"line":33,"column":null}},"8":{"start":{"line":34,"column":8},"end":{"line":36,"column":null}},"9":{"start":{"line":35,"column":19},"end":{"line":35,"column":null}},"10":{"start":{"line":40,"column":6},"end":{"line":52,"column":null}},"11":{"start":{"line":41,"column":8},"end":{"line":41,"column":null}},"12":{"start":{"line":41,"column":36},"end":{"line":41,"column":null}},"13":{"start":{"line":42,"column":21},"end":{"line":42,"column":null}},"14":{"start":{"line":43,"column":26},"end":{"line":45,"column":null}},"15":{"start":{"line":44,"column":19},"end":{"line":44,"column":null}},"16":{"start":{"line":46,"column":8},"end":{"line":51,"column":null}},"17":{"start":{"line":47,"column":10},"end":{"line":50,"column":null}},"18":{"start":{"line":54,"column":6},"end":{"line":64,"column":null}},"19":{"start":{"line":59,"column":32},"end":{"line":59,"column":null}},"20":{"start":{"line":59,"column":52},"end":{"line":59,"column":72}},"21":{"start":{"line":60,"column":36},"end":{"line":60,"column":null}},"22":{"start":{"line":60,"column":63},"end":{"line":60,"column":69}},"23":{"start":{"line":61,"column":8},"end":{"line":63,"column":null}},"24":{"start":{"line":66,"column":6},"end":{"line":81,"column":null}},"25":{"start":{"line":68,"column":10},"end":{"line":68,"column":null}},"26":{"start":{"line":69,"column":10},"end":{"line":79,"column":null}},"27":{"start":{"line":70,"column":27},"end":{"line":70,"column":null}},"28":{"start":{"line":71,"column":12},"end":{"line":71,"column":null}},"29":{"start":{"line":73,"column":12},"end":{"line":76,"column":null}},"30":{"start":{"line":78,"column":12},"end":{"line":78,"column":null}},"31":{"start":{"line":84,"column":4},"end":{"line":84,"column":null}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":15,"column":2},"end":{"line":15,"column":22}},"loc":{"start":{"line":15,"column":41},"end":{"line":15,"column":null}},"line":15},"1":{"name":"(anonymous_1)","decl":{"start":{"line":24,"column":8},"end":{"line":24,"column":16}},"loc":{"start":{"line":24,"column":79},"end":{"line":85,"column":null}},"line":24},"2":{"name":"(anonymous_2)","decl":{"start":{"line":29,"column":8},"end":{"line":29,"column":9}},"loc":{"start":{"line":29,"column":18},"end":{"line":29,"column":null}},"line":29},"3":{"name":"(anonymous_3)","decl":{"start":{"line":32,"column":45},"end":{"line":32,"column":46}},"loc":{"start":{"line":32,"column":55},"end":{"line":37,"column":7}},"line":32},"4":{"name":"(anonymous_4)","decl":{"start":{"line":35,"column":10},"end":{"line":35,"column":11}},"loc":{"start":{"line":35,"column":19},"end":{"line":35,"column":null}},"line":35},"5":{"name":"(anonymous_5)","decl":{"start":{"line":44,"column":10},"end":{"line":44,"column":11}},"loc":{"start":{"line":44,"column":19},"end":{"line":44,"column":null}},"line":44},"6":{"name":"(anonymous_6)","decl":{"start":{"line":59,"column":45},"end":{"line":59,"column":46}},"loc":{"start":{"line":59,"column":52},"end":{"line":59,"column":72}},"line":59},"7":{"name":"(anonymous_7)","decl":{"start":{"line":60,"column":56},"end":{"line":60,"column":57}},"loc":{"start":{"line":60,"column":63},"end":{"line":60,"column":69}},"line":60},"8":{"name":"(anonymous_8)","decl":{"start":{"line":67,"column":23},"end":{"line":67,"column":30}},"loc":{"start":{"line":67,"column":39},"end":{"line":80,"column":9}},"line":67}},"branchMap":{"0":{"loc":{"start":{"line":29,"column":18},"end":{"line":29,"column":null}},"type":"binary-expr","locations":[{"start":{"line":29,"column":18},"end":{"line":29,"column":45}},{"start":{"line":29,"column":45},"end":{"line":29,"column":null}}],"line":29},"1":{"loc":{"start":{"line":33,"column":21},"end":{"line":33,"column":null}},"type":"binary-expr","locations":[{"start":{"line":33,"column":21},"end":{"line":33,"column":42}},{"start":{"line":33,"column":42},"end":{"line":33,"column":null}}],"line":33},"2":{"loc":{"start":{"line":35,"column":19},"end":{"line":35,"column":null}},"type":"binary-expr","locations":[{"start":{"line":35,"column":19},"end":{"line":35,"column":39}},{"start":{"line":35,"column":39},"end":{"line":35,"column":null}}],"line":35},"3":{"loc":{"start":{"line":41,"column":8},"end":{"line":41,"column":null}},"type":"if","locations":[{"start":{"line":41,"column":8},"end":{"line":41,"column":null}},{"start":{},"end":{}}],"line":41},"4":{"loc":{"start":{"line":42,"column":21},"end":{"line":42,"column":null}},"type":"binary-expr","locations":[{"start":{"line":42,"column":21},"end":{"line":42,"column":42}},{"start":{"line":42,"column":42},"end":{"line":42,"column":null}}],"line":42},"5":{"loc":{"start":{"line":44,"column":19},"end":{"line":44,"column":null}},"type":"binary-expr","locations":[{"start":{"line":44,"column":19},"end":{"line":44,"column":39}},{"start":{"line":44,"column":39},"end":{"line":44,"column":null}}],"line":44},"6":{"loc":{"start":{"line":46,"column":8},"end":{"line":51,"column":null}},"type":"if","locations":[{"start":{"line":46,"column":8},"end":{"line":51,"column":null}},{"start":{},"end":{}}],"line":46},"7":{"loc":{"start":{"line":54,"column":6},"end":{"line":64,"column":null}},"type":"if","locations":[{"start":{"line":54,"column":6},"end":{"line":64,"column":null}},{"start":{},"end":{}}],"line":54},"8":{"loc":{"start":{"line":55,"column":8},"end":{"line":57,"column":null}},"type":"binary-expr","locations":[{"start":{"line":55,"column":8},"end":{"line":55,"column":null}},{"start":{"line":56,"column":8},"end":{"line":56,"column":null}},{"start":{"line":57,"column":8},"end":{"line":57,"column":null}}],"line":55},"9":{"loc":{"start":{"line":75,"column":21},"end":{"line":75,"column":null}},"type":"cond-expr","locations":[{"start":{"line":75,"column":42},"end":{"line":75,"column":54}},{"start":{"line":75,"column":54},"end":{"line":75,"column":null}}],"line":75}},"s":{"0":12,"1":12,"2":12,"3":12,"4":19,"5":69,"6":19,"7":48,"8":48,"9":38,"10":19,"11":48,"12":1,"13":47,"14":48,"15":41,"16":48,"17":6,"18":19,"19":3,"20":6,"21":3,"22":3,"23":3,"24":16,"25":21,"26":21,"27":21,"28":19,"29":2,"30":21,"31":9},"f":{"0":12,"1":12,"2":69,"3":48,"4":38,"5":41,"6":6,"7":3,"8":21},"b":{"0":[69,48],"1":[48,14],"2":[38,17],"3":[1,47],"4":[47,14],"5":[41,17],"6":[6,42],"7":[3,16],"8":[19,5,5],"9":[1,1]},"meta":{"lastBranch":10,"lastFunction":9,"lastStatement":32,"seen":{"s:10:20:10:Infinity":0,"f:15:2:15:22":0,"s:15:22:15:41":1,"f:24:8:24:16":1,"s:25:20:25:Infinity":2,"s:27:4:82:Infinity":3,"s:28:27:30:Infinity":4,"f:29:8:29:9":2,"s:29:18:29:Infinity":5,"b:29:18:29:45:29:45:29:Infinity":0,"s:32:25:37:Infinity":6,"f:32:45:32:46":3,"s:33:21:33:Infinity":7,"b:33:21:33:42:33:42:33:Infinity":1,"s:34:8:36:Infinity":8,"f:35:10:35:11":4,"s:35:19:35:Infinity":9,"b:35:19:35:39:35:39:35:Infinity":2,"s:40:6:52:Infinity":10,"b:41:8:41:Infinity:undefined:undefined:undefined:undefined":3,"s:41:8:41:Infinity":11,"s:41:36:41:Infinity":12,"s:42:21:42:Infinity":13,"b:42:21:42:42:42:42:42:Infinity":4,"s:43:26:45:Infinity":14,"f:44:10:44:11":5,"s:44:19:44:Infinity":15,"b:44:19:44:39:44:39:44:Infinity":5,"b:46:8:51:Infinity:undefined:undefined:undefined:undefined":6,"s:46:8:51:Infinity":16,"s:47:10:50:Infinity":17,"b:54:6:64:Infinity:undefined:undefined:undefined:undefined":7,"s:54:6:64:Infinity":18,"b:55:8:55:Infinity:56:8:56:Infinity:57:8:57:Infinity":8,"s:59:32:59:Infinity":19,"f:59:45:59:46":6,"s:59:52:59:72":20,"s:60:36:60:Infinity":21,"f:60:56:60:57":7,"s:60:63:60:69":22,"s:61:8:63:Infinity":23,"s:66:6:81:Infinity":24,"f:67:23:67:30":8,"s:68:10:68:Infinity":25,"s:69:10:79:Infinity":26,"s:70:27:70:Infinity":27,"s:71:12:71:Infinity":28,"s:73:12:76:Infinity":29,"b:75:42:75:54:75:54:75:Infinity":9,"s:78:12:78:Infinity":30,"s:84:4:84:Infinity":31}}}
1
+ {"/home/runner/work/task-runner/task-runner/src/TaskRunner.ts": {"path":"/home/runner/work/task-runner/task-runner/src/TaskRunner.ts","statementMap":{"0":{"start":{"line":52,"column":20},"end":{"line":52,"column":null}},"1":{"start":{"line":53,"column":45},"end":{"line":53,"column":null}},"2":{"start":{"line":58,"column":22},"end":{"line":58,"column":41}},"3":{"start":{"line":69,"column":4},"end":{"line":73,"column":null}},"4":{"start":{"line":72,"column":6},"end":{"line":72,"column":null}},"5":{"start":{"line":75,"column":5},"end":{"line":77,"column":null}},"6":{"start":{"line":89,"column":4},"end":{"line":93,"column":null}},"7":{"start":{"line":90,"column":7},"end":{"line":92,"column":null}},"8":{"start":{"line":105,"column":22},"end":{"line":105,"column":null}},"9":{"start":{"line":108,"column":4},"end":{"line":120,"column":null}},"10":{"start":{"line":109,"column":6},"end":{"line":119,"column":null}},"11":{"start":{"line":110,"column":8},"end":{"line":118,"column":null}},"12":{"start":{"line":111,"column":10},"end":{"line":111,"column":null}},"13":{"start":{"line":114,"column":10},"end":{"line":117,"column":null}},"14":{"start":{"line":131,"column":4},"end":{"line":131,"column":null}},"15":{"start":{"line":133,"column":20},"end":{"line":133,"column":null}},"16":{"start":{"line":135,"column":4},"end":{"line":195,"column":null}},"17":{"start":{"line":136,"column":27},"end":{"line":138,"column":null}},"18":{"start":{"line":137,"column":18},"end":{"line":137,"column":null}},"19":{"start":{"line":140,"column":25},"end":{"line":145,"column":null}},"20":{"start":{"line":141,"column":21},"end":{"line":141,"column":null}},"21":{"start":{"line":142,"column":8},"end":{"line":144,"column":null}},"22":{"start":{"line":143,"column":19},"end":{"line":143,"column":null}},"23":{"start":{"line":148,"column":6},"end":{"line":162,"column":null}},"24":{"start":{"line":149,"column":8},"end":{"line":149,"column":null}},"25":{"start":{"line":149,"column":36},"end":{"line":149,"column":null}},"26":{"start":{"line":150,"column":21},"end":{"line":150,"column":null}},"27":{"start":{"line":151,"column":26},"end":{"line":153,"column":null}},"28":{"start":{"line":152,"column":19},"end":{"line":152,"column":null}},"29":{"start":{"line":154,"column":8},"end":{"line":161,"column":null}},"30":{"start":{"line":155,"column":37},"end":{"line":158,"column":null}},"31":{"start":{"line":159,"column":10},"end":{"line":159,"column":null}},"32":{"start":{"line":160,"column":10},"end":{"line":160,"column":null}},"33":{"start":{"line":164,"column":6},"end":{"line":174,"column":null}},"34":{"start":{"line":169,"column":32},"end":{"line":169,"column":null}},"35":{"start":{"line":169,"column":52},"end":{"line":169,"column":72}},"36":{"start":{"line":170,"column":36},"end":{"line":170,"column":null}},"37":{"start":{"line":170,"column":63},"end":{"line":170,"column":69}},"38":{"start":{"line":171,"column":8},"end":{"line":173,"column":null}},"39":{"start":{"line":176,"column":6},"end":{"line":194,"column":null}},"40":{"start":{"line":178,"column":10},"end":{"line":178,"column":null}},"41":{"start":{"line":179,"column":10},"end":{"line":179,"column":null}},"42":{"start":{"line":180,"column":10},"end":{"line":192,"column":null}},"43":{"start":{"line":181,"column":27},"end":{"line":181,"column":null}},"44":{"start":{"line":182,"column":12},"end":{"line":182,"column":null}},"45":{"start":{"line":184,"column":12},"end":{"line":187,"column":null}},"46":{"start":{"line":189,"column":12},"end":{"line":189,"column":null}},"47":{"start":{"line":190,"column":27},"end":{"line":190,"column":null}},"48":{"start":{"line":191,"column":12},"end":{"line":191,"column":null}},"49":{"start":{"line":197,"column":4},"end":{"line":197,"column":null}},"50":{"start":{"line":198,"column":4},"end":{"line":198,"column":null}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":58,"column":2},"end":{"line":58,"column":22}},"loc":{"start":{"line":58,"column":41},"end":{"line":58,"column":null}},"line":58},"1":{"name":"(anonymous_1)","decl":{"start":{"line":65,"column":9},"end":{"line":65,"column":null}},"loc":{"start":{"line":68,"column":10},"end":{"line":78,"column":null}},"line":68},"2":{"name":"(anonymous_2)","decl":{"start":{"line":85,"column":9},"end":{"line":85,"column":null}},"loc":{"start":{"line":88,"column":10},"end":{"line":94,"column":null}},"line":88},"3":{"name":"(anonymous_3)","decl":{"start":{"line":101,"column":10},"end":{"line":101,"column":null}},"loc":{"start":{"line":104,"column":10},"end":{"line":121,"column":null}},"line":104},"4":{"name":"(anonymous_4)","decl":{"start":{"line":130,"column":8},"end":{"line":130,"column":16}},"loc":{"start":{"line":130,"column":79},"end":{"line":199,"column":null}},"line":130},"5":{"name":"(anonymous_5)","decl":{"start":{"line":137,"column":8},"end":{"line":137,"column":9}},"loc":{"start":{"line":137,"column":18},"end":{"line":137,"column":null}},"line":137},"6":{"name":"(anonymous_6)","decl":{"start":{"line":140,"column":45},"end":{"line":140,"column":46}},"loc":{"start":{"line":140,"column":55},"end":{"line":145,"column":7}},"line":140},"7":{"name":"(anonymous_7)","decl":{"start":{"line":143,"column":10},"end":{"line":143,"column":11}},"loc":{"start":{"line":143,"column":19},"end":{"line":143,"column":null}},"line":143},"8":{"name":"(anonymous_8)","decl":{"start":{"line":152,"column":10},"end":{"line":152,"column":11}},"loc":{"start":{"line":152,"column":19},"end":{"line":152,"column":null}},"line":152},"9":{"name":"(anonymous_9)","decl":{"start":{"line":169,"column":45},"end":{"line":169,"column":46}},"loc":{"start":{"line":169,"column":52},"end":{"line":169,"column":72}},"line":169},"10":{"name":"(anonymous_10)","decl":{"start":{"line":170,"column":56},"end":{"line":170,"column":57}},"loc":{"start":{"line":170,"column":63},"end":{"line":170,"column":69}},"line":170},"11":{"name":"(anonymous_11)","decl":{"start":{"line":177,"column":23},"end":{"line":177,"column":30}},"loc":{"start":{"line":177,"column":39},"end":{"line":193,"column":9}},"line":177}},"branchMap":{"0":{"loc":{"start":{"line":69,"column":4},"end":{"line":73,"column":null}},"type":"if","locations":[{"start":{"line":69,"column":4},"end":{"line":73,"column":null}},{"start":{},"end":{}}],"line":69},"1":{"loc":{"start":{"line":89,"column":4},"end":{"line":93,"column":null}},"type":"if","locations":[{"start":{"line":89,"column":4},"end":{"line":93,"column":null}},{"start":{},"end":{}}],"line":89},"2":{"loc":{"start":{"line":108,"column":4},"end":{"line":120,"column":null}},"type":"if","locations":[{"start":{"line":108,"column":4},"end":{"line":120,"column":null}},{"start":{},"end":{}}],"line":108},"3":{"loc":{"start":{"line":137,"column":18},"end":{"line":137,"column":null}},"type":"binary-expr","locations":[{"start":{"line":137,"column":18},"end":{"line":137,"column":45}},{"start":{"line":137,"column":45},"end":{"line":137,"column":null}}],"line":137},"4":{"loc":{"start":{"line":141,"column":21},"end":{"line":141,"column":null}},"type":"binary-expr","locations":[{"start":{"line":141,"column":21},"end":{"line":141,"column":42}},{"start":{"line":141,"column":42},"end":{"line":141,"column":null}}],"line":141},"5":{"loc":{"start":{"line":143,"column":19},"end":{"line":143,"column":null}},"type":"binary-expr","locations":[{"start":{"line":143,"column":19},"end":{"line":143,"column":39}},{"start":{"line":143,"column":39},"end":{"line":143,"column":null}}],"line":143},"6":{"loc":{"start":{"line":149,"column":8},"end":{"line":149,"column":null}},"type":"if","locations":[{"start":{"line":149,"column":8},"end":{"line":149,"column":null}},{"start":{},"end":{}}],"line":149},"7":{"loc":{"start":{"line":150,"column":21},"end":{"line":150,"column":null}},"type":"binary-expr","locations":[{"start":{"line":150,"column":21},"end":{"line":150,"column":42}},{"start":{"line":150,"column":42},"end":{"line":150,"column":null}}],"line":150},"8":{"loc":{"start":{"line":152,"column":19},"end":{"line":152,"column":null}},"type":"binary-expr","locations":[{"start":{"line":152,"column":19},"end":{"line":152,"column":39}},{"start":{"line":152,"column":39},"end":{"line":152,"column":null}}],"line":152},"9":{"loc":{"start":{"line":154,"column":8},"end":{"line":161,"column":null}},"type":"if","locations":[{"start":{"line":154,"column":8},"end":{"line":161,"column":null}},{"start":{},"end":{}}],"line":154},"10":{"loc":{"start":{"line":164,"column":6},"end":{"line":174,"column":null}},"type":"if","locations":[{"start":{"line":164,"column":6},"end":{"line":174,"column":null}},{"start":{},"end":{}}],"line":164},"11":{"loc":{"start":{"line":165,"column":8},"end":{"line":167,"column":null}},"type":"binary-expr","locations":[{"start":{"line":165,"column":8},"end":{"line":165,"column":null}},{"start":{"line":166,"column":8},"end":{"line":166,"column":null}},{"start":{"line":167,"column":8},"end":{"line":167,"column":null}}],"line":165},"12":{"loc":{"start":{"line":186,"column":21},"end":{"line":186,"column":null}},"type":"cond-expr","locations":[{"start":{"line":186,"column":42},"end":{"line":186,"column":54}},{"start":{"line":186,"column":54},"end":{"line":186,"column":null}}],"line":186}},"s":{"0":19,"1":19,"2":19,"3":12,"4":11,"5":12,"6":2,"7":1,"8":94,"9":94,"10":13,"11":13,"12":13,"13":1,"14":18,"15":18,"16":18,"17":26,"18":80,"19":26,"20":57,"21":57,"22":42,"23":26,"24":57,"25":1,"26":56,"27":57,"28":45,"29":57,"30":7,"31":7,"32":7,"33":26,"34":3,"35":6,"36":3,"37":3,"38":3,"39":23,"40":27,"41":27,"42":27,"43":27,"44":25,"45":2,"46":27,"47":27,"48":27,"49":15,"50":15},"f":{"0":19,"1":12,"2":2,"3":94,"4":18,"5":80,"6":57,"7":42,"8":45,"9":6,"10":3,"11":27},"b":{"0":[11,1],"1":[1,1],"2":[13,81],"3":[80,57],"4":[57,19],"5":[42,19],"6":[1,56],"7":[56,19],"8":[45,19],"9":[7,50],"10":[3,23],"11":[26,6,6],"12":[1,1]},"meta":{"lastBranch":13,"lastFunction":12,"lastStatement":51,"seen":{"s:52:20:52:Infinity":0,"s:53:45:53:Infinity":1,"f:58:2:58:22":0,"s:58:22:58:41":2,"f:65:9:65:Infinity":1,"b:69:4:73:Infinity:undefined:undefined:undefined:undefined":0,"s:69:4:73:Infinity":3,"s:72:6:72:Infinity":4,"s:75:5:77:Infinity":5,"f:85:9:85:Infinity":2,"b:89:4:93:Infinity:undefined:undefined:undefined:undefined":1,"s:89:4:93:Infinity":6,"s:90:7:92:Infinity":7,"f:101:10:101:Infinity":3,"s:105:22:105:Infinity":8,"b:108:4:120:Infinity:undefined:undefined:undefined:undefined":2,"s:108:4:120:Infinity":9,"s:109:6:119:Infinity":10,"s:110:8:118:Infinity":11,"s:111:10:111:Infinity":12,"s:114:10:117:Infinity":13,"f:130:8:130:16":4,"s:131:4:131:Infinity":14,"s:133:20:133:Infinity":15,"s:135:4:195:Infinity":16,"s:136:27:138:Infinity":17,"f:137:8:137:9":5,"s:137:18:137:Infinity":18,"b:137:18:137:45:137:45:137:Infinity":3,"s:140:25:145:Infinity":19,"f:140:45:140:46":6,"s:141:21:141:Infinity":20,"b:141:21:141:42:141:42:141:Infinity":4,"s:142:8:144:Infinity":21,"f:143:10:143:11":7,"s:143:19:143:Infinity":22,"b:143:19:143:39:143:39:143:Infinity":5,"s:148:6:162:Infinity":23,"b:149:8:149:Infinity:undefined:undefined:undefined:undefined":6,"s:149:8:149:Infinity":24,"s:149:36:149:Infinity":25,"s:150:21:150:Infinity":26,"b:150:21:150:42:150:42:150:Infinity":7,"s:151:26:153:Infinity":27,"f:152:10:152:11":8,"s:152:19:152:Infinity":28,"b:152:19:152:39:152:39:152:Infinity":8,"b:154:8:161:Infinity:undefined:undefined:undefined:undefined":9,"s:154:8:161:Infinity":29,"s:155:37:158:Infinity":30,"s:159:10:159:Infinity":31,"s:160:10:160:Infinity":32,"b:164:6:174:Infinity:undefined:undefined:undefined:undefined":10,"s:164:6:174:Infinity":33,"b:165:8:165:Infinity:166:8:166:Infinity:167:8:167:Infinity":11,"s:169:32:169:Infinity":34,"f:169:45:169:46":9,"s:169:52:169:72":35,"s:170:36:170:Infinity":36,"f:170:56:170:57":10,"s:170:63:170:69":37,"s:171:8:173:Infinity":38,"s:176:6:194:Infinity":39,"f:177:23:177:30":11,"s:178:10:178:Infinity":40,"s:179:10:179:Infinity":41,"s:180:10:192:Infinity":42,"s:181:27:181:Infinity":43,"s:182:12:182:Infinity":44,"s:184:12:187:Infinity":45,"b:186:42:186:54:186:54:186:Infinity":12,"s:189:12:189:Infinity":46,"s:190:27:190:Infinity":47,"s:191:12:191:Infinity":48,"s:197:4:197:Infinity":49,"s:198:4:198:Infinity":50}}}
2
2
  }
@@ -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'>32/32</span>
28
+ <span class='fraction'>51/51</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'>21/21</span>
35
+ <span class='fraction'>27/27</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'>9/9</span>
42
+ <span class='fraction'>12/12</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'>29/29</span>
49
+ <span class='fraction'>48/48</span>
50
50
  </div>
51
51
 
52
52
 
@@ -84,13 +84,13 @@
84
84
  <div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div>
85
85
  </td>
86
86
  <td data-value="100" class="pct high">100%</td>
87
- <td data-value="32" class="abs high">32/32</td>
87
+ <td data-value="51" class="abs high">51/51</td>
88
88
  <td data-value="100" class="pct high">100%</td>
89
- <td data-value="21" class="abs high">21/21</td>
89
+ <td data-value="27" class="abs high">27/27</td>
90
90
  <td data-value="100" class="pct high">100%</td>
91
- <td data-value="9" class="abs high">9/9</td>
91
+ <td data-value="12" class="abs high">12/12</td>
92
92
  <td data-value="100" class="pct high">100%</td>
93
- <td data-value="29" class="abs high">29/29</td>
93
+ <td data-value="48" class="abs high">48/48</td>
94
94
  </tr>
95
95
 
96
96
  </tbody>
@@ -101,7 +101,7 @@
101
101
  <div class='footer quiet pad2 space-top1 center small'>
102
102
  Code coverage generated by
103
103
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
104
- at 2026-01-17T23:21:19.970Z
104
+ at 2026-01-18T01:05:27.353Z
105
105
  </div>
106
106
  <script src="prettify.js"></script>
107
107
  <script>
@@ -1,5 +1,33 @@
1
1
  import { TaskStep } from "./TaskStep.js";
2
2
  import { TaskResult } from "./TaskResult.js";
3
+ /**
4
+ * Define the payload for every possible event in the lifecycle.
5
+ */
6
+ export interface RunnerEventPayloads<TContext> {
7
+ workflowStart: {
8
+ context: TContext;
9
+ steps: TaskStep<TContext>[];
10
+ };
11
+ workflowEnd: {
12
+ context: TContext;
13
+ results: Map<string, TaskResult>;
14
+ };
15
+ taskStart: {
16
+ step: TaskStep<TContext>;
17
+ };
18
+ taskEnd: {
19
+ step: TaskStep<TContext>;
20
+ result: TaskResult;
21
+ };
22
+ taskSkipped: {
23
+ step: TaskStep<TContext>;
24
+ result: TaskResult;
25
+ };
26
+ }
27
+ /**
28
+ * A generic listener type that maps the event key to its specific payload.
29
+ */
30
+ export type RunnerEventListener<TContext, K extends keyof RunnerEventPayloads<TContext>> = (data: RunnerEventPayloads<TContext>[K]) => void | Promise<void>;
3
31
  /**
4
32
  * The main class that orchestrates the execution of a list of tasks
5
33
  * based on their dependencies, with support for parallel execution.
@@ -8,10 +36,29 @@ import { TaskResult } from "./TaskResult.js";
8
36
  export declare class TaskRunner<TContext> {
9
37
  private context;
10
38
  private running;
39
+ private listeners;
11
40
  /**
12
41
  * @param context The shared context object to be passed to each task.
13
42
  */
14
43
  constructor(context: TContext);
44
+ /**
45
+ * Subscribe to an event.
46
+ * @param event The event name.
47
+ * @param callback The callback to execute when the event is emitted.
48
+ */
49
+ on<K extends keyof RunnerEventPayloads<TContext>>(event: K, callback: RunnerEventListener<TContext, K>): void;
50
+ /**
51
+ * Unsubscribe from an event.
52
+ * @param event The event name.
53
+ * @param callback The callback to remove.
54
+ */
55
+ off<K extends keyof RunnerEventPayloads<TContext>>(event: K, callback: RunnerEventListener<TContext, K>): void;
56
+ /**
57
+ * Emit an event to all subscribers.
58
+ * @param event The event name.
59
+ * @param data The payload for the event.
60
+ */
61
+ private emit;
15
62
  /**
16
63
  * Executes a list of tasks, respecting their dependencies and running
17
64
  * independent tasks in parallel.
@@ -6,12 +6,56 @@
6
6
  export class TaskRunner {
7
7
  context;
8
8
  running = new Set();
9
+ listeners = {};
9
10
  /**
10
11
  * @param context The shared context object to be passed to each task.
11
12
  */
12
13
  constructor(context) {
13
14
  this.context = context;
14
15
  }
16
+ /**
17
+ * Subscribe to an event.
18
+ * @param event The event name.
19
+ * @param callback The callback to execute when the event is emitted.
20
+ */
21
+ on(event, callback) {
22
+ if (!this.listeners[event]) {
23
+ // Type assertion needed because TypeScript cannot verify that the generic K
24
+ // matches the specific key in the mapped type during assignment.
25
+ this.listeners[event] = new Set();
26
+ }
27
+ // Type assertion needed to tell TS that this specific Set matches the callback type
28
+ this.listeners[event].add(callback);
29
+ }
30
+ /**
31
+ * Unsubscribe from an event.
32
+ * @param event The event name.
33
+ * @param callback The callback to remove.
34
+ */
35
+ off(event, callback) {
36
+ if (this.listeners[event]) {
37
+ this.listeners[event].delete(callback);
38
+ }
39
+ }
40
+ /**
41
+ * Emit an event to all subscribers.
42
+ * @param event The event name.
43
+ * @param data The payload for the event.
44
+ */
45
+ emit(event, data) {
46
+ const listeners = this.listeners[event];
47
+ if (listeners) {
48
+ for (const listener of listeners) {
49
+ try {
50
+ listener(data);
51
+ }
52
+ catch (error) {
53
+ // Prevent listener errors from bubbling up
54
+ console.error(`Error in event listener for ${String(event)}:`, error);
55
+ }
56
+ }
57
+ }
58
+ }
15
59
  /**
16
60
  * Executes a list of tasks, respecting their dependencies and running
17
61
  * independent tasks in parallel.
@@ -20,6 +64,7 @@ export class TaskRunner {
20
64
  * and values are the corresponding TaskResult objects.
21
65
  */
22
66
  async execute(steps) {
67
+ this.emit("workflowStart", { context: this.context, steps });
23
68
  const results = new Map();
24
69
  while (results.size < steps.length) {
25
70
  const pendingSteps = steps.filter((step) => !results.has(step.name) && !this.running.has(step.name));
@@ -34,10 +79,12 @@ export class TaskRunner {
34
79
  const deps = step.dependencies ?? [];
35
80
  const failedDep = deps.find((dep) => results.has(dep) && results.get(dep)?.status !== "success");
36
81
  if (failedDep) {
37
- results.set(step.name, {
82
+ const result = {
38
83
  status: "skipped",
39
84
  message: `Skipped due to failed dependency: ${failedDep}`,
40
- });
85
+ };
86
+ results.set(step.name, result);
87
+ this.emit("taskSkipped", { step, result });
41
88
  }
42
89
  }
43
90
  if (readySteps.length === 0 &&
@@ -49,6 +96,7 @@ export class TaskRunner {
49
96
  }
50
97
  await Promise.all(readySteps.map(async (step) => {
51
98
  this.running.add(step.name);
99
+ this.emit("taskStart", { step });
52
100
  try {
53
101
  const result = await step.run(this.context);
54
102
  results.set(step.name, result);
@@ -61,9 +109,12 @@ export class TaskRunner {
61
109
  }
62
110
  finally {
63
111
  this.running.delete(step.name);
112
+ const result = results.get(step.name);
113
+ this.emit("taskEnd", { step, result });
64
114
  }
65
115
  }));
66
116
  }
117
+ this.emit("workflowEnd", { context: this.context, results });
67
118
  return results;
68
119
  }
69
120
  }
@@ -1 +1 @@
1
- {"version":3,"file":"TaskRunner.js","sourceRoot":"","sources":["../src/TaskRunner.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"TaskRunner.js","sourceRoot":"","sources":["../src/TaskRunner.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@calmo/task-runner",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "",
5
5
  "repository": {
6
6
  "type": "git",
@@ -17,7 +17,7 @@
17
17
  "type": "module",
18
18
  "scripts": {
19
19
  "build": "tsc",
20
- "test": "vitest run --coverage",
20
+ "test": "tsc --noEmit -p tsconfig.test.json && vitest run --coverage",
21
21
  "lint": "eslint .",
22
22
  "lint:fix": "eslint . --fix",
23
23
  "format": "prettier --write .",
package/src/TaskRunner.ts CHANGED
@@ -1,6 +1,48 @@
1
1
  import { TaskStep } from "./TaskStep.js";
2
2
  import { TaskResult } from "./TaskResult.js";
3
3
 
4
+ /**
5
+ * Define the payload for every possible event in the lifecycle.
6
+ */
7
+ export interface RunnerEventPayloads<TContext> {
8
+ workflowStart: {
9
+ context: TContext;
10
+ steps: TaskStep<TContext>[];
11
+ };
12
+ workflowEnd: {
13
+ context: TContext;
14
+ results: Map<string, TaskResult>;
15
+ };
16
+ taskStart: {
17
+ step: TaskStep<TContext>;
18
+ };
19
+ taskEnd: {
20
+ step: TaskStep<TContext>;
21
+ result: TaskResult;
22
+ };
23
+ taskSkipped: {
24
+ step: TaskStep<TContext>;
25
+ result: TaskResult;
26
+ };
27
+ }
28
+
29
+ /**
30
+ * A generic listener type that maps the event key to its specific payload.
31
+ */
32
+ export type RunnerEventListener<
33
+ TContext,
34
+ K extends keyof RunnerEventPayloads<TContext>,
35
+ > = (data: RunnerEventPayloads<TContext>[K]) => void | Promise<void>;
36
+
37
+ /**
38
+ * Helper type for the listeners map to avoid private access issues in generic contexts.
39
+ */
40
+ type ListenerMap<TContext> = {
41
+ [K in keyof RunnerEventPayloads<TContext>]?: Set<
42
+ RunnerEventListener<TContext, K>
43
+ >;
44
+ };
45
+
4
46
  /**
5
47
  * The main class that orchestrates the execution of a list of tasks
6
48
  * based on their dependencies, with support for parallel execution.
@@ -8,12 +50,76 @@ import { TaskResult } from "./TaskResult.js";
8
50
  */
9
51
  export class TaskRunner<TContext> {
10
52
  private running = new Set<string>();
53
+ private listeners: ListenerMap<TContext> = {};
11
54
 
12
55
  /**
13
56
  * @param context The shared context object to be passed to each task.
14
57
  */
15
58
  constructor(private context: TContext) {}
16
59
 
60
+ /**
61
+ * Subscribe to an event.
62
+ * @param event The event name.
63
+ * @param callback The callback to execute when the event is emitted.
64
+ */
65
+ public on<K extends keyof RunnerEventPayloads<TContext>>(
66
+ event: K,
67
+ callback: RunnerEventListener<TContext, K>
68
+ ): void {
69
+ if (!this.listeners[event]) {
70
+ // Type assertion needed because TypeScript cannot verify that the generic K
71
+ // matches the specific key in the mapped type during assignment.
72
+ this.listeners[event] = new Set() as unknown as ListenerMap<TContext>[K];
73
+ }
74
+ // Type assertion needed to tell TS that this specific Set matches the callback type
75
+ (this.listeners[event] as Set<RunnerEventListener<TContext, K>>).add(
76
+ callback
77
+ );
78
+ }
79
+
80
+ /**
81
+ * Unsubscribe from an event.
82
+ * @param event The event name.
83
+ * @param callback The callback to remove.
84
+ */
85
+ public off<K extends keyof RunnerEventPayloads<TContext>>(
86
+ event: K,
87
+ callback: RunnerEventListener<TContext, K>
88
+ ): void {
89
+ if (this.listeners[event]) {
90
+ (this.listeners[event] as Set<RunnerEventListener<TContext, K>>).delete(
91
+ callback
92
+ );
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Emit an event to all subscribers.
98
+ * @param event The event name.
99
+ * @param data The payload for the event.
100
+ */
101
+ private emit<K extends keyof RunnerEventPayloads<TContext>>(
102
+ event: K,
103
+ data: RunnerEventPayloads<TContext>[K]
104
+ ): void {
105
+ const listeners = this.listeners[event] as
106
+ | Set<RunnerEventListener<TContext, K>>
107
+ | undefined;
108
+ if (listeners) {
109
+ for (const listener of listeners) {
110
+ try {
111
+ listener(data);
112
+ } catch (error) {
113
+ // Prevent listener errors from bubbling up
114
+ console.error(
115
+ `Error in event listener for ${String(event)}:`,
116
+ error
117
+ );
118
+ }
119
+ }
120
+ }
121
+ }
122
+
17
123
  /**
18
124
  * Executes a list of tasks, respecting their dependencies and running
19
125
  * independent tasks in parallel.
@@ -22,6 +128,8 @@ export class TaskRunner<TContext> {
22
128
  * and values are the corresponding TaskResult objects.
23
129
  */
24
130
  async execute(steps: TaskStep<TContext>[]): Promise<Map<string, TaskResult>> {
131
+ this.emit("workflowStart", { context: this.context, steps });
132
+
25
133
  const results = new Map<string, TaskResult>();
26
134
 
27
135
  while (results.size < steps.length) {
@@ -44,10 +152,12 @@ export class TaskRunner<TContext> {
44
152
  (dep) => results.has(dep) && results.get(dep)?.status !== "success"
45
153
  );
46
154
  if (failedDep) {
47
- results.set(step.name, {
155
+ const result: TaskResult = {
48
156
  status: "skipped",
49
157
  message: `Skipped due to failed dependency: ${failedDep}`,
50
- });
158
+ };
159
+ results.set(step.name, result);
160
+ this.emit("taskSkipped", { step, result });
51
161
  }
52
162
  }
53
163
 
@@ -66,6 +176,7 @@ export class TaskRunner<TContext> {
66
176
  await Promise.all(
67
177
  readySteps.map(async (step) => {
68
178
  this.running.add(step.name);
179
+ this.emit("taskStart", { step });
69
180
  try {
70
181
  const result = await step.run(this.context);
71
182
  results.set(step.name, result);
@@ -76,11 +187,14 @@ export class TaskRunner<TContext> {
76
187
  });
77
188
  } finally {
78
189
  this.running.delete(step.name);
190
+ const result = results.get(step.name)!;
191
+ this.emit("taskEnd", { step, result });
79
192
  }
80
193
  })
81
194
  );
82
195
  }
83
196
 
197
+ this.emit("workflowEnd", { context: this.context, results });
84
198
  return results;
85
199
  }
86
200
  }
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "noEmit": true,
5
+ "rootDir": "."
6
+ },
7
+ "include": ["src/**/*", "tests/**/*"]
8
+ }