@calmo/task-runner 1.1.1 → 1.2.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.
Files changed (35) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/coverage/TaskGraphValidator.ts.html +463 -0
  3. package/coverage/TaskRunner.ts.html +163 -52
  4. package/coverage/coverage-final.json +2 -1
  5. package/coverage/index.html +22 -7
  6. package/coverage/lcov-report/TaskGraphValidator.ts.html +463 -0
  7. package/coverage/lcov-report/TaskRunner.ts.html +163 -52
  8. package/coverage/lcov-report/index.html +22 -7
  9. package/coverage/lcov.info +190 -99
  10. package/dist/TaskGraph.d.ts +18 -0
  11. package/dist/TaskGraph.js +2 -0
  12. package/dist/TaskGraph.js.map +1 -0
  13. package/dist/TaskGraphValidator.d.ts +17 -0
  14. package/dist/TaskGraphValidator.js +103 -0
  15. package/dist/TaskGraphValidator.js.map +1 -0
  16. package/dist/TaskRunner.d.ts +1 -0
  17. package/dist/TaskRunner.js +44 -9
  18. package/dist/TaskRunner.js.map +1 -1
  19. package/dist/contracts/ITaskGraphValidator.d.ts +13 -0
  20. package/dist/contracts/ITaskGraphValidator.js +2 -0
  21. package/dist/contracts/ITaskGraphValidator.js.map +1 -0
  22. package/dist/contracts/ValidationError.d.ts +11 -0
  23. package/dist/contracts/ValidationError.js +2 -0
  24. package/dist/contracts/ValidationError.js.map +1 -0
  25. package/dist/contracts/ValidationResult.d.ts +10 -0
  26. package/dist/contracts/ValidationResult.js +2 -0
  27. package/dist/contracts/ValidationResult.js.map +1 -0
  28. package/package.json +1 -1
  29. package/src/TaskGraph.ts +19 -0
  30. package/src/TaskGraphValidator.ts +126 -0
  31. package/src/TaskRunner.ts +50 -13
  32. package/src/contracts/ITaskGraphValidator.ts +14 -0
  33. package/src/contracts/ValidationError.ts +11 -0
  34. package/src/contracts/ValidationResult.ts +11 -0
  35. package/test-report.xml +51 -23
@@ -25,7 +25,7 @@
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'>51/51</span>
28
+ <span class='fraction'>67/67</span>
29
29
  </div>
30
30
 
31
31
 
@@ -46,7 +46,7 @@
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'>48/48</span>
49
+ <span class='fraction'>66/66</span>
50
50
  </div>
51
51
 
52
52
 
@@ -263,7 +263,44 @@
263
263
  <a name='L198'></a><a href='#L198'>198</a>
264
264
  <a name='L199'></a><a href='#L199'>199</a>
265
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>
266
+ <a name='L201'></a><a href='#L201'>201</a>
267
+ <a name='L202'></a><a href='#L202'>202</a>
268
+ <a name='L203'></a><a href='#L203'>203</a>
269
+ <a name='L204'></a><a href='#L204'>204</a>
270
+ <a name='L205'></a><a href='#L205'>205</a>
271
+ <a name='L206'></a><a href='#L206'>206</a>
272
+ <a name='L207'></a><a href='#L207'>207</a>
273
+ <a name='L208'></a><a href='#L208'>208</a>
274
+ <a name='L209'></a><a href='#L209'>209</a>
275
+ <a name='L210'></a><a href='#L210'>210</a>
276
+ <a name='L211'></a><a href='#L211'>211</a>
277
+ <a name='L212'></a><a href='#L212'>212</a>
278
+ <a name='L213'></a><a href='#L213'>213</a>
279
+ <a name='L214'></a><a href='#L214'>214</a>
280
+ <a name='L215'></a><a href='#L215'>215</a>
281
+ <a name='L216'></a><a href='#L216'>216</a>
282
+ <a name='L217'></a><a href='#L217'>217</a>
283
+ <a name='L218'></a><a href='#L218'>218</a>
284
+ <a name='L219'></a><a href='#L219'>219</a>
285
+ <a name='L220'></a><a href='#L220'>220</a>
286
+ <a name='L221'></a><a href='#L221'>221</a>
287
+ <a name='L222'></a><a href='#L222'>222</a>
288
+ <a name='L223'></a><a href='#L223'>223</a>
289
+ <a name='L224'></a><a href='#L224'>224</a>
290
+ <a name='L225'></a><a href='#L225'>225</a>
291
+ <a name='L226'></a><a href='#L226'>226</a>
292
+ <a name='L227'></a><a href='#L227'>227</a>
293
+ <a name='L228'></a><a href='#L228'>228</a>
294
+ <a name='L229'></a><a href='#L229'>229</a>
295
+ <a name='L230'></a><a href='#L230'>230</a>
296
+ <a name='L231'></a><a href='#L231'>231</a>
297
+ <a name='L232'></a><a href='#L232'>232</a>
298
+ <a name='L233'></a><a href='#L233'>233</a>
299
+ <a name='L234'></a><a href='#L234'>234</a>
300
+ <a name='L235'></a><a href='#L235'>235</a>
301
+ <a name='L236'></a><a href='#L236'>236</a>
302
+ <a name='L237'></a><a href='#L237'>237</a>
303
+ <a name='L238'></a><a href='#L238'>238</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
267
304
  <span class="cline-any cline-neutral">&nbsp;</span>
268
305
  <span class="cline-any cline-neutral">&nbsp;</span>
269
306
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -314,13 +351,16 @@
314
351
  <span class="cline-any cline-neutral">&nbsp;</span>
315
352
  <span class="cline-any cline-neutral">&nbsp;</span>
316
353
  <span class="cline-any cline-neutral">&nbsp;</span>
317
- <span class="cline-any cline-yes">19x</span>
318
- <span class="cline-any cline-yes">19x</span>
319
354
  <span class="cline-any cline-neutral">&nbsp;</span>
320
355
  <span class="cline-any cline-neutral">&nbsp;</span>
356
+ <span class="cline-any cline-yes">22x</span>
357
+ <span class="cline-any cline-yes">22x</span>
358
+ <span class="cline-any cline-yes">22x</span>
321
359
  <span class="cline-any cline-neutral">&nbsp;</span>
322
360
  <span class="cline-any cline-neutral">&nbsp;</span>
323
- <span class="cline-any cline-yes">19x</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-yes">22x</span>
324
364
  <span class="cline-any cline-neutral">&nbsp;</span>
325
365
  <span class="cline-any cline-neutral">&nbsp;</span>
326
366
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -367,10 +407,10 @@
367
407
  <span class="cline-any cline-neutral">&nbsp;</span>
368
408
  <span class="cline-any cline-neutral">&nbsp;</span>
369
409
  <span class="cline-any cline-neutral">&nbsp;</span>
370
- <span class="cline-any cline-yes">94x</span>
410
+ <span class="cline-any cline-yes">88x</span>
371
411
  <span class="cline-any cline-neutral">&nbsp;</span>
372
412
  <span class="cline-any cline-neutral">&nbsp;</span>
373
- <span class="cline-any cline-yes">94x</span>
413
+ <span class="cline-any cline-yes">88x</span>
374
414
  <span class="cline-any cline-yes">13x</span>
375
415
  <span class="cline-any cline-yes">13x</span>
376
416
  <span class="cline-any cline-yes">13x</span>
@@ -393,67 +433,101 @@
393
433
  <span class="cline-any cline-neutral">&nbsp;</span>
394
434
  <span class="cline-any cline-neutral">&nbsp;</span>
395
435
  <span class="cline-any cline-neutral">&nbsp;</span>
396
- <span class="cline-any cline-yes">18x</span>
397
436
  <span class="cline-any cline-neutral">&nbsp;</span>
398
- <span class="cline-any cline-yes">18x</span>
437
+ <span class="cline-any cline-yes">21x</span>
438
+ <span class="cline-any cline-yes">43x</span>
399
439
  <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
440
  <span class="cline-any cline-neutral">&nbsp;</span>
404
441
  <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
442
  <span class="cline-any cline-neutral">&nbsp;</span>
410
443
  <span class="cline-any cline-neutral">&nbsp;</span>
444
+ <span class="cline-any cline-yes">21x</span>
445
+ <span class="cline-any cline-yes">21x</span>
411
446
  <span class="cline-any cline-neutral">&nbsp;</span>
447
+ <span class="cline-any cline-yes">6x</span>
448
+ <span class="cline-any cline-yes">6x</span>
412
449
  <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>
450
+ <span class="cline-any cline-yes">6x</span>
451
+ <span class="cline-any cline-yes">6x</span>
452
+ <span class="cline-any cline-yes">6x</span>
418
453
  <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
454
  <span class="cline-any cline-neutral">&nbsp;</span>
455
+ <span class="cline-any cline-yes">2x</span>
422
456
  <span class="cline-any cline-neutral">&nbsp;</span>
457
+ <span class="cline-any cline-yes">6x</span>
458
+ <span class="cline-any cline-yes">2x</span>
423
459
  <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
460
  <span class="cline-any cline-neutral">&nbsp;</span>
427
461
  <span class="cline-any cline-neutral">&nbsp;</span>
462
+ <span class="cline-any cline-yes">2x</span>
463
+ <span class="cline-any cline-yes">2x</span>
464
+ <span class="cline-any cline-yes">2x</span>
465
+ <span class="cline-any cline-neutral">&nbsp;</span>
466
+ <span class="cline-any cline-neutral">&nbsp;</span>
467
+ <span class="cline-any cline-yes">2x</span>
468
+ <span class="cline-any cline-yes">2x</span>
469
+ <span class="cline-any cline-yes">2x</span>
428
470
  <span class="cline-any cline-neutral">&nbsp;</span>
429
- <span class="cline-any cline-yes">26x</span>
430
471
  <span class="cline-any cline-neutral">&nbsp;</span>
431
472
  <span class="cline-any cline-neutral">&nbsp;</span>
432
473
  <span class="cline-any cline-neutral">&nbsp;</span>
433
474
  <span class="cline-any cline-neutral">&nbsp;</span>
434
475
  <span class="cline-any cline-yes">6x</span>
435
- <span class="cline-any cline-yes">3x</span>
436
- <span class="cline-any cline-yes">3x</span>
476
+ <span class="cline-any cline-yes">6x</span>
477
+ <span class="cline-any cline-yes">6x</span>
478
+ <span class="cline-any cline-neutral">&nbsp;</span>
479
+ <span class="cline-any cline-neutral">&nbsp;</span>
480
+ <span class="cline-any cline-yes">6x</span>
481
+ <span class="cline-any cline-neutral">&nbsp;</span>
482
+ <span class="cline-any cline-neutral">&nbsp;</span>
483
+ <span class="cline-any cline-yes">15x</span>
484
+ <span class="cline-any cline-neutral">&nbsp;</span>
485
+ <span class="cline-any cline-yes">15x</span>
486
+ <span class="cline-any cline-neutral">&nbsp;</span>
487
+ <span class="cline-any cline-yes">15x</span>
488
+ <span class="cline-any cline-yes">22x</span>
489
+ <span class="cline-any cline-yes">71x</span>
437
490
  <span class="cline-any cline-neutral">&nbsp;</span>
438
491
  <span class="cline-any cline-neutral">&nbsp;</span>
492
+ <span class="cline-any cline-yes">22x</span>
493
+ <span class="cline-any cline-yes">49x</span>
494
+ <span class="cline-any cline-yes">49x</span>
495
+ <span class="cline-any cline-yes">35x</span>
439
496
  <span class="cline-any cline-neutral">&nbsp;</span>
440
497
  <span class="cline-any cline-neutral">&nbsp;</span>
441
- <span class="cline-any cline-yes">23x</span>
442
498
  <span class="cline-any cline-neutral">&nbsp;</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>
499
+ <span class="cline-any cline-neutral">&nbsp;</span>
500
+ <span class="cline-any cline-yes">22x</span>
501
+ <span class="cline-any cline-yes">49x</span>
502
+ <span class="cline-any cline-yes">49x</span>
503
+ <span class="cline-any cline-yes">39x</span>
504
+ <span class="cline-any cline-neutral">&nbsp;</span>
505
+ <span class="cline-any cline-yes">49x</span>
506
+ <span class="cline-any cline-yes">6x</span>
507
+ <span class="cline-any cline-neutral">&nbsp;</span>
508
+ <span class="cline-any cline-neutral">&nbsp;</span>
509
+ <span class="cline-any cline-neutral">&nbsp;</span>
510
+ <span class="cline-any cline-yes">6x</span>
511
+ <span class="cline-any cline-yes">6x</span>
512
+ <span class="cline-any cline-neutral">&nbsp;</span>
513
+ <span class="cline-any cline-neutral">&nbsp;</span>
514
+ <span class="cline-any cline-neutral">&nbsp;</span>
515
+ <span class="cline-any cline-yes">22x</span>
516
+ <span class="cline-any cline-neutral">&nbsp;</span>
517
+ <span class="cline-any cline-yes">26x</span>
518
+ <span class="cline-any cline-yes">26x</span>
519
+ <span class="cline-any cline-yes">26x</span>
520
+ <span class="cline-any cline-yes">26x</span>
521
+ <span class="cline-any cline-yes">24x</span>
448
522
  <span class="cline-any cline-neutral">&nbsp;</span>
449
523
  <span class="cline-any cline-yes">2x</span>
450
524
  <span class="cline-any cline-neutral">&nbsp;</span>
451
525
  <span class="cline-any cline-neutral">&nbsp;</span>
452
526
  <span class="cline-any cline-neutral">&nbsp;</span>
453
527
  <span class="cline-any cline-neutral">&nbsp;</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>
528
+ <span class="cline-any cline-yes">26x</span>
529
+ <span class="cline-any cline-yes">26x</span>
530
+ <span class="cline-any cline-yes">26x</span>
457
531
  <span class="cline-any cline-neutral">&nbsp;</span>
458
532
  <span class="cline-any cline-neutral">&nbsp;</span>
459
533
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -465,6 +539,8 @@
465
539
  <span class="cline-any cline-neutral">&nbsp;</span>
466
540
  <span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">import { TaskStep } from "./TaskStep.js";
467
541
  import { TaskResult } from "./TaskResult.js";
542
+ import { TaskGraphValidator } from "./TaskGraphValidator.js";
543
+ import { TaskGraph } from "./TaskGraph.js";
468
544
  &nbsp;
469
545
  /**
470
546
  * Define the payload for every possible event in the lifecycle.
@@ -516,6 +592,7 @@ type ListenerMap&lt;TContext&gt; = {
516
592
  export class TaskRunner&lt;TContext&gt; {
517
593
  private running = new Set&lt;string&gt;();
518
594
  private listeners: ListenerMap&lt;TContext&gt; = {};
595
+ private validator = new TaskGraphValidator();
519
596
  &nbsp;
520
597
  /**
521
598
  * @param context The shared context object to be passed to each task.
@@ -593,6 +670,53 @@ export class TaskRunner&lt;TContext&gt; {
593
670
  * and values are the corresponding TaskResult objects.
594
671
  */
595
672
  async execute(steps: TaskStep&lt;TContext&gt;[]): Promise&lt;Map&lt;string, TaskResult&gt;&gt; {
673
+ // Validate the task graph before execution
674
+ const taskGraph: TaskGraph = {
675
+ tasks: steps.map((step) =&gt; ({
676
+ id: step.name,
677
+ dependencies: step.dependencies ?? [],
678
+ })),
679
+ };
680
+ &nbsp;
681
+ const validationResult = this.validator.validate(taskGraph);
682
+ if (!validationResult.isValid) {
683
+ // Construct error message compatible with legacy tests
684
+ const affectedTasks = new Set&lt;string&gt;();
685
+ const errorDetails: string[] = [];
686
+ &nbsp;
687
+ for (const error of validationResult.errors) {
688
+ errorDetails.push(error.message);
689
+ switch (error.type) {
690
+ case "cycle": {
691
+ // details is { cyclePath: string[] }
692
+ const path = (error.details as { cyclePath: string[] }).cyclePath;
693
+ // The last element duplicates the first in the path representation, so valid unique tasks are slice(0, -1) or just all as Set handles uniq
694
+ path.forEach((t) =&gt; affectedTasks.add(t));
695
+ break;
696
+ }
697
+ case "missing_dependency": {
698
+ // details is { taskId: string, missingDependencyId: string }
699
+ const d = error.details as { taskId: string };
700
+ affectedTasks.add(d.taskId);
701
+ break;
702
+ }
703
+ case "duplicate_task": {
704
+ const d = error.details as { taskId: string };
705
+ affectedTasks.add(d.taskId);
706
+ break;
707
+ }
708
+ }
709
+ }
710
+ &nbsp;
711
+ // Legacy error format: "Circular dependency or missing dependency detected. Unable to run tasks: A, B"
712
+ const taskList = Array.from(affectedTasks).join(", ");
713
+ const legacyMessage = `Circular dependency or missing dependency detected. Unable to run tasks: ${taskList}`;
714
+ const detailedMessage = `Task graph validation failed: ${errorDetails.join("; ")}`;
715
+ &nbsp;
716
+ // Combine them to satisfy both legacy tests (checking for legacy message) and new requirements (clear details)
717
+ throw new Error(`${legacyMessage} | ${detailedMessage}`);
718
+ }
719
+ &nbsp;
596
720
  this.emit("workflowStart", { context: this.context, steps });
597
721
  &nbsp;
598
722
  const results = new Map&lt;string, TaskResult&gt;();
@@ -611,7 +735,6 @@ export class TaskRunner&lt;TContext&gt; {
611
735
  &nbsp;
612
736
  // Skip tasks with failed dependencies
613
737
  for (const step of pendingSteps) {
614
- if (results.has(step.name)) continue;
615
738
  const deps = step.dependencies ?? [];
616
739
  const failedDep = deps.find(
617
740
  (dep) =&gt; results.has(dep) &amp;&amp; results.get(dep)?.status !== "success"
@@ -625,18 +748,6 @@ export class TaskRunner&lt;TContext&gt; {
625
748
  this.emit("taskSkipped", { step, result });
626
749
  }
627
750
  }
628
- &nbsp;
629
- if (
630
- readySteps.length === 0 &amp;&amp;
631
- this.running.size === 0 &amp;&amp;
632
- results.size &lt; steps.length
633
- ) {
634
- const unrunnableSteps = steps.filter((s) =&gt; !results.has(s.name));
635
- const unrunnableStepNames = unrunnableSteps.map((s) =&gt; s.name);
636
- throw new Error(
637
- `Circular dependency or missing dependency detected. Unable to run tasks: ${unrunnableStepNames.join(", ")}`
638
- );
639
- }
640
751
  &nbsp;
641
752
  await Promise.all(
642
753
  readySteps.map(async (step) =&gt; {
@@ -670,7 +781,7 @@ export class TaskRunner&lt;TContext&gt; {
670
781
  <div class='footer quiet pad2 space-top1 center small'>
671
782
  Code coverage generated by
672
783
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
673
- at 2026-01-18T03:50:46.213Z
784
+ at 2026-01-18T03:56:21.886Z
674
785
  </div>
675
786
  <script src="prettify.js"></script>
676
787
  <script>
@@ -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'>51/51</span>
28
+ <span class='fraction'>110/110</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'>27/27</span>
35
+ <span class='fraction'>43/43</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'>12/12</span>
42
+ <span class='fraction'>15/15</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'>48/48</span>
49
+ <span class='fraction'>108/108</span>
50
50
  </div>
51
51
 
52
52
 
@@ -79,18 +79,33 @@
79
79
  </tr>
80
80
  </thead>
81
81
  <tbody><tr>
82
+ <td class="file high" data-value="TaskGraphValidator.ts"><a href="TaskGraphValidator.ts.html">TaskGraphValidator.ts</a></td>
83
+ <td data-value="100" class="pic high">
84
+ <div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div>
85
+ </td>
86
+ <td data-value="100" class="pct high">100%</td>
87
+ <td data-value="43" class="abs high">43/43</td>
88
+ <td data-value="100" class="pct high">100%</td>
89
+ <td data-value="16" class="abs high">16/16</td>
90
+ <td data-value="100" class="pct high">100%</td>
91
+ <td data-value="3" class="abs high">3/3</td>
92
+ <td data-value="100" class="pct high">100%</td>
93
+ <td data-value="42" class="abs high">42/42</td>
94
+ </tr>
95
+
96
+ <tr>
82
97
  <td class="file high" data-value="TaskRunner.ts"><a href="TaskRunner.ts.html">TaskRunner.ts</a></td>
83
98
  <td data-value="100" class="pic high">
84
99
  <div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div>
85
100
  </td>
86
101
  <td data-value="100" class="pct high">100%</td>
87
- <td data-value="51" class="abs high">51/51</td>
102
+ <td data-value="67" class="abs high">67/67</td>
88
103
  <td data-value="100" class="pct high">100%</td>
89
104
  <td data-value="27" class="abs high">27/27</td>
90
105
  <td data-value="100" class="pct high">100%</td>
91
106
  <td data-value="12" class="abs high">12/12</td>
92
107
  <td data-value="100" class="pct high">100%</td>
93
- <td data-value="48" class="abs high">48/48</td>
108
+ <td data-value="66" class="abs high">66/66</td>
94
109
  </tr>
95
110
 
96
111
  </tbody>
@@ -101,7 +116,7 @@
101
116
  <div class='footer quiet pad2 space-top1 center small'>
102
117
  Code coverage generated by
103
118
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
104
- at 2026-01-18T03:50:46.213Z
119
+ at 2026-01-18T03:56:21.886Z
105
120
  </div>
106
121
  <script src="prettify.js"></script>
107
122
  <script>