modestbench 0.2.0 → 0.3.1

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 (357) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +131 -34
  3. package/dist/cli/commands/analyze.cjs +60 -0
  4. package/dist/cli/commands/analyze.cjs.map +1 -0
  5. package/dist/cli/commands/analyze.d.cts +35 -0
  6. package/dist/cli/commands/analyze.d.cts.map +1 -0
  7. package/dist/cli/commands/analyze.d.ts +35 -0
  8. package/dist/cli/commands/analyze.d.ts.map +1 -0
  9. package/dist/cli/commands/analyze.js +56 -0
  10. package/dist/cli/commands/analyze.js.map +1 -0
  11. package/dist/cli/commands/baseline.cjs +404 -0
  12. package/dist/cli/commands/baseline.cjs.map +1 -0
  13. package/dist/cli/commands/baseline.d.cts +72 -0
  14. package/dist/cli/commands/baseline.d.cts.map +1 -0
  15. package/dist/cli/commands/baseline.d.ts +72 -0
  16. package/dist/cli/commands/baseline.d.ts.map +1 -0
  17. package/dist/cli/commands/baseline.js +396 -0
  18. package/dist/cli/commands/baseline.js.map +1 -0
  19. package/dist/cli/commands/history.d.cts +1 -1
  20. package/dist/cli/commands/history.d.cts.map +1 -1
  21. package/dist/cli/commands/history.d.ts +1 -1
  22. package/dist/cli/commands/history.d.ts.map +1 -1
  23. package/dist/cli/commands/init.cjs +99 -166
  24. package/dist/cli/commands/init.cjs.map +1 -1
  25. package/dist/cli/commands/init.d.cts +4 -4
  26. package/dist/cli/commands/init.d.cts.map +1 -1
  27. package/dist/cli/commands/init.d.ts +4 -4
  28. package/dist/cli/commands/init.d.ts.map +1 -1
  29. package/dist/cli/commands/init.js +99 -166
  30. package/dist/cli/commands/init.js.map +1 -1
  31. package/dist/cli/commands/run.cjs +146 -127
  32. package/dist/cli/commands/run.cjs.map +1 -1
  33. package/dist/cli/commands/run.d.cts +16 -3
  34. package/dist/cli/commands/run.d.cts.map +1 -1
  35. package/dist/cli/commands/run.d.ts +16 -3
  36. package/dist/cli/commands/run.d.ts.map +1 -1
  37. package/dist/cli/commands/run.js +145 -93
  38. package/dist/cli/commands/run.js.map +1 -1
  39. package/dist/cli/index.cjs +583 -394
  40. package/dist/cli/index.cjs.map +1 -1
  41. package/dist/cli/index.d.cts +4 -16
  42. package/dist/cli/index.d.cts.map +1 -1
  43. package/dist/cli/index.d.ts +4 -16
  44. package/dist/cli/index.d.ts.map +1 -1
  45. package/dist/cli/index.js +575 -386
  46. package/dist/cli/index.js.map +1 -1
  47. package/dist/config/budget-schema.cjs +172 -0
  48. package/dist/config/budget-schema.cjs.map +1 -0
  49. package/dist/config/budget-schema.d.cts +59 -0
  50. package/dist/config/budget-schema.d.cts.map +1 -0
  51. package/dist/config/budget-schema.d.ts +59 -0
  52. package/dist/config/budget-schema.d.ts.map +1 -0
  53. package/dist/config/budget-schema.js +166 -0
  54. package/dist/config/budget-schema.js.map +1 -0
  55. package/dist/config/schema.cjs +182 -2
  56. package/dist/config/schema.cjs.map +1 -1
  57. package/dist/config/schema.d.cts +122 -3
  58. package/dist/config/schema.d.cts.map +1 -1
  59. package/dist/config/schema.d.ts +122 -3
  60. package/dist/config/schema.d.ts.map +1 -1
  61. package/dist/config/schema.js +180 -1
  62. package/dist/config/schema.js.map +1 -1
  63. package/dist/constants.cjs +45 -2
  64. package/dist/constants.cjs.map +1 -1
  65. package/dist/constants.d.cts +41 -0
  66. package/dist/constants.d.cts.map +1 -1
  67. package/dist/constants.d.ts +41 -0
  68. package/dist/constants.d.ts.map +1 -1
  69. package/dist/constants.js +44 -1
  70. package/dist/constants.js.map +1 -1
  71. package/dist/core/engine.cjs +114 -23
  72. package/dist/core/engine.cjs.map +1 -1
  73. package/dist/core/engine.d.cts +7 -7
  74. package/dist/core/engine.d.cts.map +1 -1
  75. package/dist/core/engine.d.ts +7 -7
  76. package/dist/core/engine.d.ts.map +1 -1
  77. package/dist/core/engine.js +115 -24
  78. package/dist/core/engine.js.map +1 -1
  79. package/dist/core/engines/accurate-engine.cjs +171 -36
  80. package/dist/core/engines/accurate-engine.cjs.map +1 -1
  81. package/dist/core/engines/accurate-engine.d.cts +5 -0
  82. package/dist/core/engines/accurate-engine.d.cts.map +1 -1
  83. package/dist/core/engines/accurate-engine.d.ts +5 -0
  84. package/dist/core/engines/accurate-engine.d.ts.map +1 -1
  85. package/dist/core/engines/accurate-engine.js +171 -36
  86. package/dist/core/engines/accurate-engine.js.map +1 -1
  87. package/dist/core/engines/tinybench-engine.cjs +3 -2
  88. package/dist/core/engines/tinybench-engine.cjs.map +1 -1
  89. package/dist/core/engines/tinybench-engine.d.cts.map +1 -1
  90. package/dist/core/engines/tinybench-engine.d.ts.map +1 -1
  91. package/dist/core/engines/tinybench-engine.js +3 -2
  92. package/dist/core/engines/tinybench-engine.js.map +1 -1
  93. package/dist/core/output-path-resolver.cjs +8 -1
  94. package/dist/core/output-path-resolver.cjs.map +1 -1
  95. package/dist/core/output-path-resolver.d.cts.map +1 -1
  96. package/dist/core/output-path-resolver.d.ts.map +1 -1
  97. package/dist/core/output-path-resolver.js +9 -2
  98. package/dist/core/output-path-resolver.js.map +1 -1
  99. package/dist/errors/base.cjs +12 -3
  100. package/dist/errors/base.cjs.map +1 -1
  101. package/dist/errors/base.d.cts +7 -0
  102. package/dist/errors/base.d.cts.map +1 -1
  103. package/dist/errors/base.d.ts +7 -0
  104. package/dist/errors/base.d.ts.map +1 -1
  105. package/dist/errors/base.js +10 -2
  106. package/dist/errors/base.js.map +1 -1
  107. package/dist/errors/budget.cjs +37 -0
  108. package/dist/errors/budget.cjs.map +1 -0
  109. package/dist/errors/budget.d.cts +31 -0
  110. package/dist/errors/budget.d.cts.map +1 -0
  111. package/dist/errors/budget.d.ts +31 -0
  112. package/dist/errors/budget.d.ts.map +1 -0
  113. package/dist/errors/budget.js +33 -0
  114. package/dist/errors/budget.js.map +1 -0
  115. package/dist/errors/index.cjs +4 -1
  116. package/dist/errors/index.cjs.map +1 -1
  117. package/dist/errors/index.d.cts +1 -0
  118. package/dist/errors/index.d.cts.map +1 -1
  119. package/dist/errors/index.d.ts +1 -0
  120. package/dist/errors/index.d.ts.map +1 -1
  121. package/dist/errors/index.js +2 -0
  122. package/dist/errors/index.js.map +1 -1
  123. package/dist/index.cjs +13 -1
  124. package/dist/index.cjs.map +1 -1
  125. package/dist/index.d.cts +5 -0
  126. package/dist/index.d.cts.map +1 -1
  127. package/dist/index.d.ts +5 -0
  128. package/dist/index.d.ts.map +1 -1
  129. package/dist/index.js +7 -0
  130. package/dist/index.js.map +1 -1
  131. package/dist/reporters/csv.cjs +37 -17
  132. package/dist/reporters/csv.cjs.map +1 -1
  133. package/dist/reporters/csv.d.cts +3 -6
  134. package/dist/reporters/csv.d.cts.map +1 -1
  135. package/dist/reporters/csv.d.ts +3 -6
  136. package/dist/reporters/csv.d.ts.map +1 -1
  137. package/dist/reporters/csv.js +37 -17
  138. package/dist/reporters/csv.js.map +1 -1
  139. package/dist/reporters/human.cjs +290 -67
  140. package/dist/reporters/human.cjs.map +1 -1
  141. package/dist/reporters/human.d.cts +25 -13
  142. package/dist/reporters/human.d.cts.map +1 -1
  143. package/dist/reporters/human.d.ts +25 -13
  144. package/dist/reporters/human.d.ts.map +1 -1
  145. package/dist/reporters/human.js +290 -67
  146. package/dist/reporters/human.js.map +1 -1
  147. package/dist/reporters/json.cjs +23 -48
  148. package/dist/reporters/json.cjs.map +1 -1
  149. package/dist/reporters/json.d.cts +2 -28
  150. package/dist/reporters/json.d.cts.map +1 -1
  151. package/dist/reporters/json.d.ts +2 -28
  152. package/dist/reporters/json.d.ts.map +1 -1
  153. package/dist/reporters/json.js +25 -50
  154. package/dist/reporters/json.js.map +1 -1
  155. package/dist/reporters/profile-human.cjs +154 -0
  156. package/dist/reporters/profile-human.cjs.map +1 -0
  157. package/dist/reporters/profile-human.d.cts +44 -0
  158. package/dist/reporters/profile-human.d.cts.map +1 -0
  159. package/dist/reporters/profile-human.d.ts +44 -0
  160. package/dist/reporters/profile-human.d.ts.map +1 -0
  161. package/dist/reporters/profile-human.js +147 -0
  162. package/dist/reporters/profile-human.js.map +1 -0
  163. package/dist/reporters/simple.cjs +67 -45
  164. package/dist/reporters/simple.cjs.map +1 -1
  165. package/dist/reporters/simple.d.cts +14 -14
  166. package/dist/reporters/simple.d.cts.map +1 -1
  167. package/dist/reporters/simple.d.ts +14 -14
  168. package/dist/reporters/simple.d.ts.map +1 -1
  169. package/dist/reporters/simple.js +67 -45
  170. package/dist/reporters/simple.js.map +1 -1
  171. package/dist/schema/modestbench-config.schema.json +153 -0
  172. package/dist/services/baseline-storage.cjs +151 -0
  173. package/dist/services/baseline-storage.cjs.map +1 -0
  174. package/dist/services/baseline-storage.d.cts +55 -0
  175. package/dist/services/baseline-storage.d.cts.map +1 -0
  176. package/dist/services/baseline-storage.d.ts +55 -0
  177. package/dist/services/baseline-storage.d.ts.map +1 -0
  178. package/dist/services/baseline-storage.js +147 -0
  179. package/dist/services/baseline-storage.js.map +1 -0
  180. package/dist/services/budget-evaluator.cjs +146 -0
  181. package/dist/services/budget-evaluator.cjs.map +1 -0
  182. package/dist/services/budget-evaluator.d.cts +29 -0
  183. package/dist/services/budget-evaluator.d.cts.map +1 -0
  184. package/dist/services/budget-evaluator.d.ts +29 -0
  185. package/dist/services/budget-evaluator.d.ts.map +1 -0
  186. package/dist/services/budget-evaluator.js +142 -0
  187. package/dist/services/budget-evaluator.js.map +1 -0
  188. package/dist/services/config-manager.cjs +24 -10
  189. package/dist/services/config-manager.cjs.map +1 -1
  190. package/dist/services/config-manager.d.cts +6 -1
  191. package/dist/services/config-manager.d.cts.map +1 -1
  192. package/dist/services/config-manager.d.ts +6 -1
  193. package/dist/services/config-manager.d.ts.map +1 -1
  194. package/dist/services/config-manager.js +24 -10
  195. package/dist/services/config-manager.js.map +1 -1
  196. package/dist/services/file-loader.cjs +3 -6
  197. package/dist/services/file-loader.cjs.map +1 -1
  198. package/dist/services/file-loader.d.cts.map +1 -1
  199. package/dist/services/file-loader.d.ts.map +1 -1
  200. package/dist/services/file-loader.js +3 -6
  201. package/dist/services/file-loader.js.map +1 -1
  202. package/dist/services/profiler/profile-filter.cjs +116 -0
  203. package/dist/services/profiler/profile-filter.cjs.map +1 -0
  204. package/dist/services/profiler/profile-filter.d.cts +20 -0
  205. package/dist/services/profiler/profile-filter.d.cts.map +1 -0
  206. package/dist/services/profiler/profile-filter.d.ts +20 -0
  207. package/dist/services/profiler/profile-filter.d.ts.map +1 -0
  208. package/dist/services/profiler/profile-filter.js +112 -0
  209. package/dist/services/profiler/profile-filter.js.map +1 -0
  210. package/dist/services/profiler/profile-parser.cjs +139 -0
  211. package/dist/services/profiler/profile-parser.cjs.map +1 -0
  212. package/dist/services/profiler/profile-parser.d.cts +18 -0
  213. package/dist/services/profiler/profile-parser.d.cts.map +1 -0
  214. package/dist/services/profiler/profile-parser.d.ts +18 -0
  215. package/dist/services/profiler/profile-parser.d.ts.map +1 -0
  216. package/dist/services/profiler/profile-parser.js +132 -0
  217. package/dist/services/profiler/profile-parser.js.map +1 -0
  218. package/dist/services/profiler/profile-runner.cjs +90 -0
  219. package/dist/services/profiler/profile-runner.cjs.map +1 -0
  220. package/dist/services/profiler/profile-runner.d.cts +29 -0
  221. package/dist/services/profiler/profile-runner.d.cts.map +1 -0
  222. package/dist/services/profiler/profile-runner.d.ts +29 -0
  223. package/dist/services/profiler/profile-runner.d.ts.map +1 -0
  224. package/dist/services/profiler/profile-runner.js +86 -0
  225. package/dist/services/profiler/profile-runner.js.map +1 -0
  226. package/dist/services/progress-manager.cjs +10 -2
  227. package/dist/services/progress-manager.cjs.map +1 -1
  228. package/dist/services/progress-manager.d.cts +2 -0
  229. package/dist/services/progress-manager.d.cts.map +1 -1
  230. package/dist/services/progress-manager.d.ts +2 -0
  231. package/dist/services/progress-manager.d.ts.map +1 -1
  232. package/dist/services/progress-manager.js +10 -2
  233. package/dist/services/progress-manager.js.map +1 -1
  234. package/dist/services/reporter-registry.cjs +18 -24
  235. package/dist/services/reporter-registry.cjs.map +1 -1
  236. package/dist/services/reporter-registry.d.cts +18 -40
  237. package/dist/services/reporter-registry.d.cts.map +1 -1
  238. package/dist/services/reporter-registry.d.ts +18 -40
  239. package/dist/services/reporter-registry.d.ts.map +1 -1
  240. package/dist/services/reporter-registry.js +18 -24
  241. package/dist/services/reporter-registry.js.map +1 -1
  242. package/dist/types/budgets.cjs +8 -0
  243. package/dist/types/budgets.cjs.map +1 -0
  244. package/dist/types/budgets.d.cts +149 -0
  245. package/dist/types/budgets.d.cts.map +1 -0
  246. package/dist/types/budgets.d.ts +149 -0
  247. package/dist/types/budgets.d.ts.map +1 -0
  248. package/dist/types/budgets.js +7 -0
  249. package/dist/types/budgets.js.map +1 -0
  250. package/dist/types/cli.cjs +2 -11
  251. package/dist/types/cli.cjs.map +1 -1
  252. package/dist/types/cli.d.cts +3 -227
  253. package/dist/types/cli.d.cts.map +1 -1
  254. package/dist/types/cli.d.ts +3 -227
  255. package/dist/types/cli.d.ts.map +1 -1
  256. package/dist/types/cli.js +2 -11
  257. package/dist/types/cli.js.map +1 -1
  258. package/dist/types/core.cjs +6 -1
  259. package/dist/types/core.cjs.map +1 -1
  260. package/dist/types/core.d.cts +15 -2
  261. package/dist/types/core.d.cts.map +1 -1
  262. package/dist/types/core.d.ts +15 -2
  263. package/dist/types/core.d.ts.map +1 -1
  264. package/dist/types/core.js +2 -1
  265. package/dist/types/core.js.map +1 -1
  266. package/dist/types/index.cjs +5 -0
  267. package/dist/types/index.cjs.map +1 -1
  268. package/dist/types/index.d.cts +2 -0
  269. package/dist/types/index.d.cts.map +1 -1
  270. package/dist/types/index.d.ts +2 -0
  271. package/dist/types/index.d.ts.map +1 -1
  272. package/dist/types/index.js +2 -0
  273. package/dist/types/index.js.map +1 -1
  274. package/dist/types/interfaces.d.cts +19 -8
  275. package/dist/types/interfaces.d.cts.map +1 -1
  276. package/dist/types/interfaces.d.ts +19 -8
  277. package/dist/types/interfaces.d.ts.map +1 -1
  278. package/dist/types/profiler.cjs +11 -0
  279. package/dist/types/profiler.cjs.map +1 -0
  280. package/dist/types/profiler.d.cts +102 -0
  281. package/dist/types/profiler.d.cts.map +1 -0
  282. package/dist/types/profiler.d.ts +102 -0
  283. package/dist/types/profiler.d.ts.map +1 -0
  284. package/dist/types/profiler.js +10 -0
  285. package/dist/types/profiler.js.map +1 -0
  286. package/dist/types/utility.cjs.map +1 -1
  287. package/dist/types/utility.d.cts +0 -8
  288. package/dist/types/utility.d.cts.map +1 -1
  289. package/dist/types/utility.d.ts +0 -8
  290. package/dist/types/utility.d.ts.map +1 -1
  291. package/dist/types/utility.js.map +1 -1
  292. package/dist/utils/identifiers.cjs +32 -0
  293. package/dist/utils/identifiers.cjs.map +1 -0
  294. package/dist/utils/identifiers.d.cts +32 -0
  295. package/dist/utils/identifiers.d.cts.map +1 -0
  296. package/dist/utils/identifiers.d.ts +32 -0
  297. package/dist/utils/identifiers.d.ts.map +1 -0
  298. package/dist/utils/identifiers.js +27 -0
  299. package/dist/utils/identifiers.js.map +1 -0
  300. package/dist/utils/package.cjs +40 -0
  301. package/dist/utils/package.cjs.map +1 -0
  302. package/dist/utils/package.d.cts +15 -0
  303. package/dist/utils/package.d.cts.map +1 -0
  304. package/dist/utils/package.d.ts +15 -0
  305. package/dist/utils/package.d.ts.map +1 -0
  306. package/dist/utils/package.js +33 -0
  307. package/dist/utils/package.js.map +1 -0
  308. package/dist/utils/type-guards.cjs +48 -0
  309. package/dist/utils/type-guards.cjs.map +1 -0
  310. package/dist/utils/type-guards.d.cts +22 -0
  311. package/dist/utils/type-guards.d.cts.map +1 -0
  312. package/dist/utils/type-guards.d.ts +22 -0
  313. package/dist/utils/type-guards.d.ts.map +1 -0
  314. package/dist/utils/type-guards.js +43 -0
  315. package/dist/utils/type-guards.js.map +1 -0
  316. package/package.json +18 -19
  317. package/src/cli/commands/analyze.ts +101 -0
  318. package/src/cli/commands/baseline.ts +577 -0
  319. package/src/cli/commands/history.ts +1 -1
  320. package/src/cli/commands/init.ts +116 -194
  321. package/src/cli/commands/run.ts +183 -113
  322. package/src/cli/index.ts +425 -183
  323. package/src/config/budget-schema.ts +189 -0
  324. package/src/config/schema.ts +260 -1
  325. package/src/constants.ts +53 -1
  326. package/src/core/engine.ts +169 -22
  327. package/src/core/engines/accurate-engine.ts +195 -44
  328. package/src/core/engines/tinybench-engine.ts +3 -2
  329. package/src/core/output-path-resolver.ts +10 -2
  330. package/src/errors/base.ts +11 -2
  331. package/src/errors/budget.ts +38 -0
  332. package/src/errors/index.ts +3 -0
  333. package/src/index.ts +9 -0
  334. package/src/reporters/csv.ts +54 -25
  335. package/src/reporters/human.ts +434 -115
  336. package/src/reporters/json.ts +26 -71
  337. package/src/reporters/profile-human.ts +210 -0
  338. package/src/reporters/simple.ts +88 -54
  339. package/src/services/baseline-storage.ts +199 -0
  340. package/src/services/budget-evaluator.ts +182 -0
  341. package/src/services/config-manager.ts +24 -9
  342. package/src/services/file-loader.ts +3 -6
  343. package/src/services/profiler/profile-filter.ts +147 -0
  344. package/src/services/profiler/profile-parser.ts +194 -0
  345. package/src/services/profiler/profile-runner.ts +121 -0
  346. package/src/services/progress-manager.ts +12 -2
  347. package/src/services/reporter-registry.ts +46 -81
  348. package/src/types/budgets.ts +180 -0
  349. package/src/types/cli.ts +5 -238
  350. package/src/types/core.ts +52 -10
  351. package/src/types/index.ts +5 -0
  352. package/src/types/interfaces.ts +24 -6
  353. package/src/types/profiler.ts +135 -0
  354. package/src/types/utility.ts +0 -10
  355. package/src/utils/identifiers.ts +58 -0
  356. package/src/utils/package.ts +35 -0
  357. package/src/utils/type-guards.ts +51 -0
@@ -19,8 +19,10 @@ const ansi_js_1 = require("../utils/ansi.cjs");
19
19
  class HumanReporter extends reporter_registry_js_1.BaseReporter {
20
20
  currentFile = '';
21
21
  currentSuite = '';
22
+ currentSuiteMaxNameLen = 0; // Track max name length for current suite alignment
22
23
  failures = [];
23
24
  lastProgressLine = '';
25
+ maxTimePadWidth = 0; // Track maximum time padding width to prevent jitter
24
26
  progressWindowActive = false; // Track if progress window is rendered
25
27
  quiet;
26
28
  showProgress;
@@ -40,6 +42,63 @@ class HumanReporter extends reporter_registry_js_1.BaseReporter {
40
42
  this.quiet = options.quiet ?? false;
41
43
  this.showProgress = options.progress ?? true;
42
44
  }
45
+ /**
46
+ * Format bytes in human-readable format
47
+ */
48
+ static formatBytes(bytes) {
49
+ const units = ['B', 'KB', 'MB', 'GB', 'TB'];
50
+ let size = bytes;
51
+ let unitIndex = 0;
52
+ while (size >= 1024 && unitIndex < units.length - 1) {
53
+ size /= 1024;
54
+ unitIndex++;
55
+ }
56
+ return `${size.toFixed(1)} ${units[unitIndex]}`;
57
+ }
58
+ /**
59
+ * Format file path - show relative path if within CWD, otherwise absolute
60
+ */
61
+ static formatPath(filePath) {
62
+ const cwd = process.cwd();
63
+ const absolutePath = node_path_1.default.resolve(filePath);
64
+ // Check if the file is within the current working directory
65
+ if (absolutePath.startsWith(cwd + node_path_1.default.sep) || absolutePath === cwd) {
66
+ return node_path_1.default.relative(cwd, absolutePath);
67
+ }
68
+ return absolutePath;
69
+ }
70
+ /**
71
+ * Simple pluralization helper
72
+ */
73
+ static pluralize(str, count) {
74
+ return count === 1 ? str : `${str}s`;
75
+ }
76
+ onBudgetResult(summary) {
77
+ if (summary.total === 0 || this.quiet) {
78
+ return;
79
+ }
80
+ this.clearProgress();
81
+ this.printLine();
82
+ const budgetHeader = `${this.colorize('magenta', ansi_js_1.ansiChars.block.full.repeat(2))} ${this.colorize('brightWhite', this.colorize('bold', 'Performance Budgets'))}`;
83
+ this.printLine(budgetHeader);
84
+ this.printLine();
85
+ for (const result of summary.results) {
86
+ const icon = result.passed ? ansi_js_1.ansiChars.checkmark : ansi_js_1.ansiChars.cross;
87
+ const iconColor = result.passed ? 'brightCyan' : 'brightRed';
88
+ this.printLine(` ${this.colorize(iconColor, icon)} ${this.colorize('white', result.taskId)}`);
89
+ if (!result.passed && result.violations.length > 0) {
90
+ for (const violation of result.violations) {
91
+ this.printLine(` ${this.colorize('brightRed', violation.message)}`);
92
+ }
93
+ }
94
+ }
95
+ this.printLine();
96
+ const statusText = summary.failed === 0
97
+ ? `${this.colorize('brightCyan', ansi_js_1.ansiChars.checkmark)} All ${summary.total} budget(s) passed`
98
+ : `${this.colorize('brightRed', ansi_js_1.ansiChars.cross)} ${summary.failed} of ${summary.total} budget(s) failed`;
99
+ this.printLine(` ${statusText}`);
100
+ this.printLine();
101
+ }
43
102
  onEnd(run) {
44
103
  if (this.quiet) {
45
104
  return;
@@ -51,11 +110,13 @@ class HumanReporter extends reporter_registry_js_1.BaseReporter {
51
110
  let totalSuites = 0;
52
111
  let totalPassed = 0;
53
112
  let totalFailed = 0;
113
+ let totalAborted = 0;
54
114
  for (const file of run.files) {
55
115
  totalSuites += file.suites.length;
56
116
  for (const suite of file.suites) {
57
- totalPassed += suite.tasks.filter((t) => !t.error).length;
117
+ totalPassed += suite.tasks.filter((t) => !t.error && !t.aborted).length;
58
118
  totalFailed += suite.tasks.filter((t) => t.error).length;
119
+ totalAborted += suite.tasks.filter((t) => t.aborted).length;
59
120
  }
60
121
  }
61
122
  // Results header
@@ -64,15 +125,22 @@ class HumanReporter extends reporter_registry_js_1.BaseReporter {
64
125
  this.printLine();
65
126
  this.printLine(`${this.colorize('brightBlue', ' Files:')} ${this.colorize('brightWhite', String(totalFiles))}`);
66
127
  this.printLine(`${this.colorize('brightBlue', ' Suites:')} ${this.colorize('brightWhite', String(totalSuites))}`);
67
- this.printLine(`${this.colorize('brightBlue', ' Tasks:')} ${this.colorize('brightWhite', String(totalPassed + totalFailed))}`);
68
- if (totalFailed > 0) {
69
- this.printLine(`${this.colorize('brightRed', ansi_js_1.ansiChars.cross + ' Failed:')} ${this.colorize('brightWhite', String(totalFailed))}`);
70
- this.printLine(`${this.colorize('brightCyan', ansi_js_1.ansiChars.checkmark + ' Passed:')} ${this.colorize('brightWhite', String(totalPassed))}`);
128
+ this.printLine(`${this.colorize('brightBlue', ' Tasks:')} ${this.colorize('brightWhite', String(totalPassed + totalFailed + totalAborted))}`);
129
+ if (totalFailed > 0 || totalAborted > 0) {
130
+ if (totalFailed > 0) {
131
+ this.printLine(`${this.colorize('brightRed', ansi_js_1.ansiChars.cross + ' Failed:')} ${this.colorize('brightWhite', String(totalFailed))}`);
132
+ }
133
+ if (totalPassed > 0) {
134
+ this.printLine(`${this.colorize('brightCyan', ansi_js_1.ansiChars.checkmark + ' Passed:')} ${this.colorize('brightWhite', String(totalPassed))}`);
135
+ }
136
+ if (totalAborted > 0) {
137
+ this.printLine(`${this.colorize('brightYellow', ansi_js_1.ansiChars.approx + ' Aborted:')} ${this.colorize('brightWhite', String(totalAborted))}`);
138
+ }
71
139
  }
72
140
  else {
73
141
  this.printLine(`${this.colorize('brightCyan', ansi_js_1.ansiChars.checkmark + ' All tasks passed:')} ${this.colorize('brightWhite', String(totalPassed))}`);
74
142
  }
75
- this.printLine(`${this.colorize('cyan', ansi_js_1.ansiChars.approx + ' Duration:')} ${this.colorize('brightWhite', this.formatDuration(duration * 1000000))}`);
143
+ this.printLine(`${this.colorize('cyan', ansi_js_1.ansiChars.approx + ' Duration:')} ${this.colorize('brightWhite', reporter_registry_js_1.BaseReporter.formatDuration(duration * 1000000))}`);
76
144
  this.printLine();
77
145
  if (totalFailed > 0) {
78
146
  // Display failed tasks with details
@@ -81,14 +149,15 @@ class HumanReporter extends reporter_registry_js_1.BaseReporter {
81
149
  this.printLine(this.colorize('brightRed', this.colorize('bold', 'Failed Tasks:')));
82
150
  this.printLine();
83
151
  for (const failure of this.failures) {
84
- const displayPath = this.formatPath(failure.file);
152
+ const displayPath = HumanReporter.formatPath(failure.file);
85
153
  this.printLine(` ${this.colorize('dim', displayPath)} ${this.colorize('dim', '›')} ${this.colorize('white', failure.suite)} ${this.colorize('dim', '›')} ${this.colorize('brightWhite', failure.task)}`);
86
154
  this.printLine(` ${this.colorize('brightRed', failure.error)}`);
87
155
  this.printLine();
88
156
  }
89
157
  }
90
158
  }
91
- else {
159
+ else if (totalAborted === 0) {
160
+ // Only show "Rad" if no failures AND no aborts
92
161
  const successMessage = `${this.colorize('brightMagenta', 'Rad. ☮')}`;
93
162
  this.printLine(successMessage);
94
163
  }
@@ -114,7 +183,7 @@ class HumanReporter extends reporter_registry_js_1.BaseReporter {
114
183
  this.printLine(this.colorize('red', ` ${ansi_js_1.ansiChars.cross} ${totalFailed} failed, ${totalPassed} passed`));
115
184
  }
116
185
  else {
117
- this.printLine(` ${this.colorize('magenta', ansi_js_1.ansiChars.checkmark)} ${totalPassed > 1 ? this.colorize('brightMagenta', 'All ') : ''}${this.colorize('bold', this.colorize('brightMagenta', `${totalPassed}`))} ${this.colorize('brightMagenta', `${this.pluralize('task', totalPassed)} passed`)}`);
186
+ this.printLine(` ${this.colorize('magenta', ansi_js_1.ansiChars.checkmark)} ${totalPassed > 1 ? this.colorize('brightMagenta', 'All ') : ''}${this.colorize('bold', this.colorize('brightMagenta', `${totalPassed}`))} ${this.colorize('brightMagenta', `${HumanReporter.pluralize('task', totalPassed)} passed`)}`);
118
187
  }
119
188
  this.printLine();
120
189
  }
@@ -123,7 +192,7 @@ class HumanReporter extends reporter_registry_js_1.BaseReporter {
123
192
  if (this.quiet) {
124
193
  return;
125
194
  }
126
- const displayPath = this.formatPath(file);
195
+ const displayPath = HumanReporter.formatPath(file);
127
196
  const fileMarker = `${ansi_js_1.colors.magenta}${ansi_js_1.ansiChars.block.dark}${ansi_js_1.ansiChars.block.dark}${ansi_js_1.colors.reset}`;
128
197
  this.printLine(`${fileMarker} ${ansi_js_1.colors.underline}${this.colorize('brightMagenta', this.colorize('bold', displayPath))}${ansi_js_1.colors.reset}`);
129
198
  }
@@ -136,7 +205,7 @@ class HumanReporter extends reporter_registry_js_1.BaseReporter {
136
205
  if (!process.stdout.isTTY) {
137
206
  return;
138
207
  }
139
- const { elapsed, percentage, tasksCompleted, totalTasks } = state;
208
+ const { currentTask, elapsed, percentage, tasksCompleted, totalTasks } = state;
140
209
  // Pad task counts for alignment
141
210
  const totalTasksWidth = String(totalTasks).length;
142
211
  const paddedTasksCompleted = String(tasksCompleted).padStart(totalTasksWidth, ' ');
@@ -145,7 +214,7 @@ class HumanReporter extends reporter_registry_js_1.BaseReporter {
145
214
  const elapsedStrRaw = this.formatTimeRemaining(elapsedSeconds);
146
215
  // Calculate ETA if we have completed tasks and determine padding width
147
216
  let etaStr = '';
148
- let padWidth = elapsedStrRaw.length;
217
+ let padWidth = Math.max(this.maxTimePadWidth, elapsedStrRaw.length);
149
218
  if (tasksCompleted > 0) {
150
219
  const avgTimePerTask = elapsed / tasksCompleted;
151
220
  const remainingTasks = totalTasks - tasksCompleted;
@@ -153,12 +222,21 @@ class HumanReporter extends reporter_registry_js_1.BaseReporter {
153
222
  const etaSeconds = Math.round(etaMs / 1000);
154
223
  const etaTimeStr = this.formatTimeRemaining(etaSeconds);
155
224
  padWidth = Math.max(padWidth, etaTimeStr.length);
156
- etaStr = ` ${this.colorize('dim', '|')} ${this.colorize('dim', 'ETA:')} ${this.colorize('brightBlue', etaTimeStr)}`;
225
+ etaStr = ` ${this.colorize('gray', '|')} ${this.colorize('gray', 'ETA:')} ${this.colorize('brightBlue', etaTimeStr)}`;
157
226
  }
227
+ // Remember the maximum width we've ever used to prevent jitter
228
+ this.maxTimePadWidth = Math.max(this.maxTimePadWidth, padWidth);
158
229
  // Pad elapsed time to match the longest time string
159
- const elapsedStr = elapsedStrRaw.padStart(padWidth, ' ');
230
+ const elapsedStr = elapsedStrRaw.padStart(this.maxTimePadWidth, ' ');
160
231
  const roundedPercentage = percentage.toFixed(2);
161
- const line = `${this.colorize('brightCyan', ansi_js_1.ansiChars.approx)} ${this.colorize('white', paddedTasksCompleted)}${this.colorize('dim', '/')}${this.colorize('white', String(totalTasks))} ${this.colorize('dim', 'tasks')} ${this.colorize('dim', '(')}${this.colorize('brightBlue', roundedPercentage + '%')}${this.colorize('dim', ')')} ${this.colorize('dim', '|')} ${this.colorize('dim', 'Elapsed:')} ${this.colorize('cyan', elapsedStr)}${etaStr}`;
232
+ // Build progress line with current task if available
233
+ let line = `${this.colorize('brightCyan', ansi_js_1.ansiChars.approx)} ${this.colorize('white', paddedTasksCompleted)}${this.colorize('gray', '/')}${this.colorize('white', String(totalTasks))} ${this.colorize('gray', 'tasks')} ${this.colorize('gray', '(')}${this.colorize('brightBlue', roundedPercentage + '%')}${this.colorize('gray', ')')} ${this.colorize('gray', '|')} ${this.colorize('gray', 'Elapsed:')} ${this.colorize('cyan', elapsedStr)}${etaStr}`;
234
+ if (currentTask) {
235
+ const truncatedTask = currentTask.length > 60
236
+ ? currentTask.substring(0, 57) + '...'
237
+ : currentTask;
238
+ line += ` ${this.colorize('gray', '|')} ${this.colorize('white', truncatedTask)}`;
239
+ }
162
240
  this.lastProgressLine = line;
163
241
  this.renderProgressWindow();
164
242
  }
@@ -166,6 +244,7 @@ class HumanReporter extends reporter_registry_js_1.BaseReporter {
166
244
  this.startTime = Date.now();
167
245
  this.failures = []; // Reset failures for new run
168
246
  this.lastProgressLine = ''; // Reset for new run
247
+ this.maxTimePadWidth = 0; // Reset time padding width for new run
169
248
  if (this.quiet) {
170
249
  return;
171
250
  }
@@ -181,7 +260,7 @@ class HumanReporter extends reporter_registry_js_1.BaseReporter {
181
260
  \x1b[48;5;0m \x1b[48;5;14m \x1b[38;5;30;48;5;38m▄\x1b[38;5;14;48;5;14m▄\x1b[48;5;14m \x1b[38;5;45;48;5;14m▄\x1b[38;5;89;48;5;14m▄\x1b[38;5;89;48;5;89m▄\x1b[38;5;14;48;5;31m▄\x1b[48;5;14m \x1b[38;5;37;48;5;89m▄\x1b[48;5;198m \x1b[38;5;198;48;5;198m▄\x1b[38;5;31;48;5;14m▄\x1b[48;5;14m \x1b[48;5;0m \x1b[m \x1b[2mnode.js:\x1b[m \x1b[36m${run.environment.nodeVersion} \x1b[m
182
261
  \x1b[48;5;0m \x1b[48;5;14m \x1b[38;5;44;48;5;31m▄\x1b[48;5;14m \x1b[38;5;126;48;5;38m▄\x1b[38;5;198;48;5;237m▄\x1b[38;5;237;48;5;37m▄\x1b[48;5;14m \x1b[38;5;14;48;5;14m▄\x1b[38;5;162;48;5;198m▄▄\x1b[38;5;53;48;5;240m▄\x1b[48;5;14m \x1b[48;5;0m \x1b[m \x1b[2mplatform:\x1b[m \x1b[36m${run.environment.platform} ${run.environment.arch} \x1b[m
183
262
  \x1b[48;5;0m \x1b[38;5;45;48;5;14m▄\x1b[48;5;14m \x1b[38;5;14;48;5;37m▄\x1b[38;5;14;48;5;5m▄\x1b[38;5;14;48;5;44m▄\x1b[48;5;14m \x1b[38;5;45;48;5;14m▄\x1b[48;5;0m \x1b[m \x1b[2mcpu:\x1b[m \x1b[36m${run.environment.cpu.model} \x1b[2m(\x1b[m\x1b[36m${run.environment.cpu.cores} cores\x1b[2m) \x1b[m
184
- \x1b[49;38;5;0m▀▀\x1b[38;5;0;48;5;6m▄\x1b[38;5;232;48;5;14m▄\x1b[38;5;38;48;5;14m▄\x1b[48;5;14m \x1b[38;5;30;48;5;14m▄\x1b[38;5;0;48;5;14m▄\x1b[38;5;0;48;5;23m▄\x1b[49;38;5;0m▀▀\x1b[m \x1b[2mmem:\x1b[m \x1b[36m${this.formatBytes(run.environment.memory.total)} \x1b[m
263
+ \x1b[49;38;5;0m▀▀\x1b[38;5;0;48;5;6m▄\x1b[38;5;232;48;5;14m▄\x1b[38;5;38;48;5;14m▄\x1b[48;5;14m \x1b[38;5;30;48;5;14m▄\x1b[38;5;0;48;5;14m▄\x1b[38;5;0;48;5;23m▄\x1b[49;38;5;0m▀▀\x1b[m \x1b[2mmem:\x1b[m \x1b[36m${HumanReporter.formatBytes(run.environment.memory.total)} \x1b[m
185
264
  \x1b[49m \x1b[49;38;5;0m▀\x1b[38;5;0;48;5;236m▄\x1b[38;5;0;48;5;45m▄\x1b[38;5;23;48;5;14m▄\x1b[48;5;14m \x1b[38;5;236;48;5;14m▄\x1b[38;5;0;48;5;44m▄\x1b[38;5;0;48;5;232m▄\x1b[49;38;5;0m▀\x1b[49m \x1b[m
186
265
  \x1b[49m \x1b[49;38;5;0m▀▀\x1b[38;5;0;48;5;37m▄\x1b[38;5;0;48;5;14m▄\x1b[38;5;0;48;5;30m▄\x1b[49;38;5;0m▀▀\x1b[49m \x1b[m
187
266
  `;
@@ -216,22 +295,53 @@ class HumanReporter extends reporter_registry_js_1.BaseReporter {
216
295
  if (this.quiet) {
217
296
  return;
218
297
  }
219
- // Print all buffered task results with aligned columns
220
- this.printAlignedSuiteResults();
298
+ // Tasks are printed immediately in onTaskResult, so just print suite summary
221
299
  // Skip displaying summary for the implicit "default" suite
222
300
  if (result.name === 'default') {
223
301
  return;
224
302
  }
225
- const passed = result.tasks.filter((t) => !t.error).length;
303
+ const passed = result.tasks.filter((t) => !t.error && !t.aborted).length;
226
304
  const failed = result.tasks.filter((t) => t.error).length;
305
+ const aborted = result.tasks.filter((t) => t.aborted).length;
306
+ const durationStr = reporter_registry_js_1.BaseReporter.formatDuration(result.duration * 1000000); // ms to ns
307
+ // Build summary parts
308
+ const parts = [];
227
309
  if (failed > 0) {
228
- this.printLine(` ${this.colorize('red', `${ansi_js_1.ansiChars.cross} ${failed} failed`)}, ${this.colorize('green', `${passed} passed`)}`);
310
+ parts.push(this.colorize('red', `${ansi_js_1.ansiChars.cross} ${failed} failed`));
311
+ }
312
+ if (passed > 0) {
313
+ parts.push(this.colorize('green', `${passed} passed`));
314
+ }
315
+ if (aborted > 0) {
316
+ parts.push(this.colorize('brightYellow', `${aborted} aborted`));
317
+ }
318
+ const summary = parts.join(', ');
319
+ const timeInfo = `${this.colorize('gray', 'in')} ${this.colorize('cyan', durationStr)}`;
320
+ if (failed > 0 || aborted > 0) {
321
+ this.printLine(` ${summary} ${timeInfo}`);
229
322
  }
230
323
  else {
231
- this.printLine(` ${this.colorize('magenta', ansi_js_1.ansiChars.checkmark)} ${this.colorize('bold', this.colorize('brightWhite', `${passed}`))} ${this.colorize('brightWhite', `${this.pluralize('task', passed)} passed`)}`);
324
+ this.printLine(` ${this.colorize('magenta', ansi_js_1.ansiChars.checkmark)} ${this.colorize('bold', this.colorize('brightWhite', `${passed}`))} ${this.colorize('brightWhite', `${HumanReporter.pluralize('task', passed)} passed`)} ${timeInfo}`);
232
325
  }
233
326
  this.printLine();
234
327
  }
328
+ onSuiteInit(suite, taskNames) {
329
+ // Pre-calculate max name length for optimal alignment
330
+ const terminalWidth = process.stdout.columns || 80;
331
+ const STATS_RESERVED_WIDTH = 70;
332
+ const MAX_NAME_WIDTH = Math.max(40, Math.min(60, terminalWidth - 4 - 2 - 2 - STATS_RESERVED_WIDTH));
333
+ // Calculate the actual max name length from non-wrapped names
334
+ let maxLen = 0;
335
+ for (const name of taskNames) {
336
+ const nameLen = this.getVisibleLength(name.trim());
337
+ // Only count names that won't wrap
338
+ if (nameLen <= MAX_NAME_WIDTH) {
339
+ maxLen = Math.max(maxLen, nameLen);
340
+ }
341
+ }
342
+ // Use the max of actual names or MAX_NAME_WIDTH for consistency
343
+ this.currentSuiteMaxNameLen = Math.max(maxLen, MAX_NAME_WIDTH);
344
+ }
235
345
  onSuiteStart(suite) {
236
346
  this.currentSuite = suite;
237
347
  if (this.quiet) {
@@ -250,8 +360,14 @@ class HumanReporter extends reporter_registry_js_1.BaseReporter {
250
360
  if (this.quiet) {
251
361
  return;
252
362
  }
253
- // Buffer the result for later printing with proper alignment
363
+ // Always buffer the result for suite summary (including aborted tasks)
254
364
  this.suiteResults.push(result);
365
+ // Skip printing aborted tasks (they're counted in summary but not shown individually)
366
+ if (result.aborted) {
367
+ return;
368
+ }
369
+ // Print immediately with current alignment
370
+ this.printTaskResult(result);
255
371
  }
256
372
  onTaskStart(task) {
257
373
  if (this.quiet) {
@@ -291,31 +407,6 @@ class HumanReporter extends reporter_registry_js_1.BaseReporter {
291
407
  }
292
408
  return `${ansi_js_1.colors[color]}${text}${ansi_js_1.colors.reset}`;
293
409
  }
294
- /**
295
- * Format bytes in human-readable format
296
- */
297
- formatBytes(bytes) {
298
- const units = ['B', 'KB', 'MB', 'GB', 'TB'];
299
- let size = bytes;
300
- let unitIndex = 0;
301
- while (size >= 1024 && unitIndex < units.length - 1) {
302
- size /= 1024;
303
- unitIndex++;
304
- }
305
- return `${size.toFixed(1)} ${units[unitIndex]}`;
306
- }
307
- /**
308
- * Format file path - show relative path if within CWD, otherwise absolute
309
- */
310
- formatPath(filePath) {
311
- const cwd = process.cwd();
312
- const absolutePath = node_path_1.default.resolve(filePath);
313
- // Check if the file is within the current working directory
314
- if (absolutePath.startsWith(cwd + node_path_1.default.sep) || absolutePath === cwd) {
315
- return node_path_1.default.relative(cwd, absolutePath);
316
- }
317
- return absolutePath;
318
- }
319
410
  /**
320
411
  * Format duration in human-readable format for progress display
321
412
  */
@@ -342,12 +433,6 @@ class HumanReporter extends reporter_registry_js_1.BaseReporter {
342
433
  // eslint-disable-next-line no-control-regex
343
434
  return str.replace(/\x1b\[[0-9;]*m/g, '').length;
344
435
  }
345
- /**
346
- * Simple pluralization helper
347
- */
348
- pluralize(str, count) {
349
- return count === 1 ? str : `${str}s`;
350
- }
351
436
  /**
352
437
  * Print all task results in a suite with aligned columns
353
438
  */
@@ -355,10 +440,17 @@ class HumanReporter extends reporter_registry_js_1.BaseReporter {
355
440
  if (this.suiteResults.length === 0) {
356
441
  return;
357
442
  }
358
- const MAX_NAME_WIDTH = 60;
359
443
  const BASE_INDENT = ' '; // 4 spaces
360
444
  const bullet = this.colorize('dim', this.colorize('gray', ansi_js_1.ansiChars.bullet));
361
- const formatted = this.suiteResults.map((result) => {
445
+ // Calculate maximum name width based on terminal width
446
+ // Reserve space for: indent (4) + status (1) + space (1) + name + ": " (2) + stats (~60 chars)
447
+ const terminalWidth = process.stdout.columns || 80;
448
+ const STATS_RESERVED_WIDTH = 70; // Approx space for duration + rme + ops/sec with padding
449
+ const MAX_NAME_WIDTH = Math.max(40, Math.min(60, terminalWidth - BASE_INDENT.length - 2 - 2 - STATS_RESERVED_WIDTH));
450
+ // Filter out aborted tasks (they're counted in suite summary but not printed)
451
+ const formatted = this.suiteResults
452
+ .filter((result) => !result.aborted)
453
+ .map((result) => {
362
454
  const status = result.error
363
455
  ? this.colorize('red', ansi_js_1.ansiChars.cross)
364
456
  : this.colorize('brightCyan', ansi_js_1.ansiChars.checkmark);
@@ -380,9 +472,9 @@ class HumanReporter extends reporter_registry_js_1.BaseReporter {
380
472
  status,
381
473
  };
382
474
  }
383
- const duration = this.formatDuration(result.mean); // already in nanoseconds
384
- const opsPerSec = this.formatOpsPerSecond(result.opsPerSecond);
385
- const rme = this.formatPercentage(result.marginOfError * 100);
475
+ const duration = reporter_registry_js_1.BaseReporter.formatDuration(result.mean); // already in nanoseconds
476
+ const opsPerSec = reporter_registry_js_1.BaseReporter.formatOpsPerSecond(result.opsPerSecond);
477
+ const rme = reporter_registry_js_1.BaseReporter.formatPercentage(result.marginOfError); // already a percentage
386
478
  return {
387
479
  durationLen: this.getVisibleLength(duration),
388
480
  durationStr: duration,
@@ -405,9 +497,6 @@ class HumanReporter extends reporter_registry_js_1.BaseReporter {
405
497
  const maxDurationLen = Math.max(...formatted.filter((t) => !t.error).map((t) => t.durationLen), 0);
406
498
  const maxRmeLen = Math.max(...formatted.filter((t) => !t.error).map((t) => t.rmeLen), 0);
407
499
  const maxOpsLen = Math.max(...formatted.filter((t) => !t.error).map((t) => t.opsPerSecLen), 0);
408
- // Calculate the position where numbers start for unwrapped lines
409
- // BASE_INDENT (4) + status (1 char) + space (1) + maxNameLen + ": " (2) = 8 + maxNameLen
410
- const numbersStartPos = BASE_INDENT.length + 2 + maxNameLen + 2;
411
500
  // Print each task with aligned columns
412
501
  for (const task of formatted) {
413
502
  if (task.error) {
@@ -421,15 +510,33 @@ class HumanReporter extends reporter_registry_js_1.BaseReporter {
421
510
  this.printLine(`${BASE_INDENT}${task.status} ${this.colorize('white', task.name)} ${this.colorize('red', 'FAILED')}`);
422
511
  }
423
512
  else if (task.nameLength > MAX_NAME_WIDTH) {
424
- // Long name - wrap to next line, but align numbers with unwrapped lines
425
- this.printLine(`${BASE_INDENT}${task.status} ${this.colorize('white', task.name)}:`);
426
- // Calculate padding to align with unwrapped lines
427
- // We need to get to numbersStartPos from the beginning of the line
428
- const leadingPad = ' '.repeat(numbersStartPos);
513
+ // Long name - wrap to multiple lines, align last line with short names
514
+ const wrappedLines = this.wrapText(task.name, MAX_NAME_WIDTH);
515
+ const continueIndent = BASE_INDENT + ' '; // 6 spaces for continuation lines
516
+ // Format stats string
429
517
  const durationPad = ' '.repeat(maxDurationLen - task.durationLen);
430
518
  const rmePad = ' '.repeat(maxRmeLen - task.rmeLen);
431
519
  const opsPad = ' '.repeat(maxOpsLen - task.opsPerSecLen);
432
- this.printLine(`${leadingPad}${durationPad}${this.colorize('cyan', task.durationStr)} ${bullet} ${ansi_js_1.ansiChars.plusMinus}${rmePad}${this.colorize('brightBlue', task.rmeStr)} ${bullet} ${opsPad}${this.colorize('magenta', task.opsPerSecStr)}`);
520
+ const statsStr = `${durationPad}${this.colorize('cyan', task.durationStr)} ${bullet} ${ansi_js_1.ansiChars.plusMinus}${rmePad}${this.colorize('brightBlue', task.rmeStr)} ${bullet} ${opsPad}${this.colorize('magenta', task.opsPerSecStr)}`;
521
+ // Print first line with status
522
+ this.printLine(`${BASE_INDENT}${task.status} ${this.colorize('white', wrappedLines[0])}`);
523
+ // Print middle continuation lines (all but first and last)
524
+ for (let i = 1; i < wrappedLines.length - 1; i++) {
525
+ this.printLine(`${continueIndent}${this.colorize('white', wrappedLines[i])}`);
526
+ }
527
+ // Print last line with colon and stats aligned with short names
528
+ if (wrappedLines.length > 1) {
529
+ const lastLine = wrappedLines[wrappedLines.length - 1];
530
+ const lastLineLen = this.getVisibleLength(lastLine);
531
+ // Pad the last line to align the ':' with short names
532
+ const lastLinePad = ' '.repeat(Math.max(0, maxNameLen - lastLineLen));
533
+ this.printLine(`${continueIndent}${this.colorize('white', lastLine)}${lastLinePad}: ${statsStr}`);
534
+ }
535
+ else {
536
+ // Single wrapped line
537
+ const lastLinePad = ' '.repeat(maxNameLen - task.nameLength);
538
+ this.printLine(`${BASE_INDENT}${task.status} ${this.colorize('white', task.name)}${lastLinePad}: ${statsStr}`);
539
+ }
433
540
  if (this.verbose && task.iterations > 0) {
434
541
  this.printLine(` ${this.colorize('dim', `${task.iterations} iterations`)}`);
435
542
  }
@@ -457,6 +564,84 @@ class HumanReporter extends reporter_registry_js_1.BaseReporter {
457
564
  console.log(message);
458
565
  this.renderProgressWindow();
459
566
  }
567
+ /**
568
+ * Print a single task result immediately with current alignment
569
+ */
570
+ printTaskResult(result) {
571
+ // Clear progress bar temporarily
572
+ this.clearProgress();
573
+ const BASE_INDENT = ' '; // 4 spaces
574
+ const bullet = this.colorize('dim', this.colorize('gray', ansi_js_1.ansiChars.bullet));
575
+ // Calculate terminal width constraints
576
+ const terminalWidth = process.stdout.columns || 80;
577
+ const STATS_RESERVED_WIDTH = 70;
578
+ const MAX_NAME_WIDTH = Math.max(40, Math.min(60, terminalWidth - BASE_INDENT.length - 2 - 2 - STATS_RESERVED_WIDTH));
579
+ // Status marker
580
+ const status = result.error
581
+ ? this.colorize('red', ansi_js_1.ansiChars.cross)
582
+ : this.colorize('brightCyan', ansi_js_1.ansiChars.checkmark);
583
+ const name = result.name.trim();
584
+ const nameLength = this.getVisibleLength(name);
585
+ // Handle errors
586
+ if (result.error) {
587
+ this.failures.push({
588
+ error: result.error?.message || String(result.error),
589
+ file: this.currentFile,
590
+ suite: this.currentSuite,
591
+ task: name,
592
+ });
593
+ this.printLine(`${BASE_INDENT}${status} ${this.colorize('white', name)} ${this.colorize('red', 'FAILED')}`);
594
+ return;
595
+ }
596
+ // Format stats
597
+ const duration = reporter_registry_js_1.BaseReporter.formatDuration(result.mean);
598
+ const opsPerSec = reporter_registry_js_1.BaseReporter.formatOpsPerSecond(result.opsPerSecond);
599
+ const rme = reporter_registry_js_1.BaseReporter.formatPercentage(result.marginOfError);
600
+ // Use fixed widths for stats columns (reasonable maximums)
601
+ const DURATION_WIDTH = 10; // "999.99ms" max
602
+ const RME_WIDTH = 8; // "±999.99%" max
603
+ const OPS_WIDTH = 15; // "999.99K ops/sec" max
604
+ const durationLen = this.getVisibleLength(duration);
605
+ const rmeLen = this.getVisibleLength(rme);
606
+ const opsLen = this.getVisibleLength(opsPerSec);
607
+ // Stats formatting with fixed widths
608
+ const durationPad = ' '.repeat(DURATION_WIDTH - durationLen);
609
+ const rmePad = ' '.repeat(RME_WIDTH - rmeLen);
610
+ const opsPad = ' '.repeat(OPS_WIDTH - opsLen);
611
+ const statsStr = `${durationPad}${this.colorize('cyan', duration)} ${bullet} ${ansi_js_1.ansiChars.plusMinus}${rmePad}${this.colorize('brightBlue', rme)} ${bullet} ${opsPad}${this.colorize('magenta', opsPerSec)}`;
612
+ // Handle long names (wrap)
613
+ if (nameLength > MAX_NAME_WIDTH) {
614
+ const wrappedLines = this.wrapText(name, MAX_NAME_WIDTH);
615
+ const continueIndent = BASE_INDENT + ' '; // 6 spaces for continuation lines
616
+ // Print first line with status
617
+ this.printLine(`${BASE_INDENT}${status} ${this.colorize('white', wrappedLines[0])}`);
618
+ // Print middle lines (all but first and last)
619
+ for (let i = 1; i < wrappedLines.length - 1; i++) {
620
+ this.printLine(`${continueIndent}${this.colorize('white', wrappedLines[i])}`);
621
+ }
622
+ // Print last line with colon and stats aligned
623
+ // Use pre-calculated currentSuiteMaxNameLen for perfect alignment
624
+ if (wrappedLines.length > 1) {
625
+ const lastLine = wrappedLines[wrappedLines.length - 1];
626
+ const lastLineLen = this.getVisibleLength(lastLine);
627
+ const lastLinePad = ' '.repeat(Math.max(0, this.currentSuiteMaxNameLen - lastLineLen));
628
+ this.printLine(`${continueIndent}${this.colorize('white', lastLine)}${lastLinePad}: ${statsStr}`);
629
+ }
630
+ else {
631
+ // Single wrapped line (shouldn't happen if nameLength > MAX but handle it)
632
+ const lastLinePad = ' '.repeat(Math.max(0, this.currentSuiteMaxNameLen - nameLength));
633
+ this.printLine(`${BASE_INDENT}${status} ${this.colorize('white', name)}${lastLinePad}: ${statsStr}`);
634
+ }
635
+ }
636
+ else {
637
+ // Normal length - print on same line with pre-calculated alignment
638
+ const namePad = ' '.repeat(Math.max(0, this.currentSuiteMaxNameLen - nameLength));
639
+ this.printLine(`${BASE_INDENT}${status} ${this.colorize('white', name)}${namePad}: ${statsStr}`);
640
+ }
641
+ if (this.verbose && result.iterations > 0) {
642
+ this.printLine(` ${this.colorize('dim', `${result.iterations} iterations`)}`);
643
+ }
644
+ }
460
645
  /**
461
646
  * Render the progress window at the bottom
462
647
  */
@@ -473,6 +658,44 @@ class HumanReporter extends reporter_registry_js_1.BaseReporter {
473
658
  console.log(this.lastProgressLine);
474
659
  this.progressWindowActive = true;
475
660
  }
661
+ /**
662
+ * Wrap text to a maximum width, breaking at word boundaries when possible
663
+ */
664
+ wrapText(text, maxWidth) {
665
+ if (this.getVisibleLength(text) <= maxWidth) {
666
+ return [text];
667
+ }
668
+ const lines = [];
669
+ let currentLine = '';
670
+ const words = text.split(/(\s+)/); // Keep whitespace in split
671
+ for (const word of words) {
672
+ const testLine = currentLine + word;
673
+ if (this.getVisibleLength(testLine) <= maxWidth) {
674
+ currentLine = testLine;
675
+ }
676
+ else {
677
+ // If current line has content, save it
678
+ if (currentLine.trim()) {
679
+ lines.push(currentLine.trimEnd());
680
+ currentLine = word.trim() + ' ';
681
+ }
682
+ else {
683
+ // Single word is too long, force break it
684
+ if (this.getVisibleLength(word) > maxWidth) {
685
+ lines.push(word.substring(0, maxWidth));
686
+ currentLine = word.substring(maxWidth);
687
+ }
688
+ else {
689
+ currentLine = word;
690
+ }
691
+ }
692
+ }
693
+ }
694
+ if (currentLine.trim()) {
695
+ lines.push(currentLine.trimEnd());
696
+ }
697
+ return lines;
698
+ }
476
699
  }
477
700
  exports.HumanReporter = HumanReporter;
478
701
  //# sourceMappingURL=human.js.map