modestbench 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (484) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +162 -57
  3. package/dist/bootstrap.cjs +10 -10
  4. package/dist/bootstrap.cjs.map +1 -1
  5. package/dist/bootstrap.d.cts.map +1 -1
  6. package/dist/bootstrap.d.ts.map +1 -1
  7. package/dist/bootstrap.js +5 -5
  8. package/dist/bootstrap.js.map +1 -1
  9. package/dist/cli/commands/analyze.cjs +60 -0
  10. package/dist/cli/commands/analyze.cjs.map +1 -0
  11. package/dist/cli/commands/analyze.d.cts +35 -0
  12. package/dist/cli/commands/analyze.d.cts.map +1 -0
  13. package/dist/cli/commands/analyze.d.ts +35 -0
  14. package/dist/cli/commands/analyze.d.ts.map +1 -0
  15. package/dist/cli/commands/analyze.js +56 -0
  16. package/dist/cli/commands/analyze.js.map +1 -0
  17. package/dist/cli/commands/baseline.cjs +404 -0
  18. package/dist/cli/commands/baseline.cjs.map +1 -0
  19. package/dist/cli/commands/baseline.d.cts +72 -0
  20. package/dist/cli/commands/baseline.d.cts.map +1 -0
  21. package/dist/cli/commands/baseline.d.ts +72 -0
  22. package/dist/cli/commands/baseline.d.ts.map +1 -0
  23. package/dist/cli/commands/baseline.js +396 -0
  24. package/dist/cli/commands/baseline.js.map +1 -0
  25. package/dist/cli/commands/history.cjs +108 -266
  26. package/dist/cli/commands/history.cjs.map +1 -1
  27. package/dist/cli/commands/history.d.cts +75 -12
  28. package/dist/cli/commands/history.d.cts.map +1 -1
  29. package/dist/cli/commands/history.d.ts +75 -12
  30. package/dist/cli/commands/history.d.ts.map +1 -1
  31. package/dist/cli/commands/history.js +105 -268
  32. package/dist/cli/commands/history.js.map +1 -1
  33. package/dist/cli/commands/init.cjs +88 -155
  34. package/dist/cli/commands/init.cjs.map +1 -1
  35. package/dist/cli/commands/init.d.cts +4 -4
  36. package/dist/cli/commands/init.d.cts.map +1 -1
  37. package/dist/cli/commands/init.d.ts +4 -4
  38. package/dist/cli/commands/init.d.ts.map +1 -1
  39. package/dist/cli/commands/init.js +88 -155
  40. package/dist/cli/commands/init.js.map +1 -1
  41. package/dist/cli/commands/run.cjs +143 -112
  42. package/dist/cli/commands/run.cjs.map +1 -1
  43. package/dist/cli/commands/run.d.cts +17 -3
  44. package/dist/cli/commands/run.d.cts.map +1 -1
  45. package/dist/cli/commands/run.d.ts +17 -3
  46. package/dist/cli/commands/run.d.ts.map +1 -1
  47. package/dist/cli/commands/run.js +142 -78
  48. package/dist/cli/commands/run.js.map +1 -1
  49. package/dist/cli/index.cjs +671 -266
  50. package/dist/cli/index.cjs.map +1 -1
  51. package/dist/cli/index.d.cts +4 -16
  52. package/dist/cli/index.d.cts.map +1 -1
  53. package/dist/cli/index.d.ts +4 -16
  54. package/dist/cli/index.d.ts.map +1 -1
  55. package/dist/cli/index.js +664 -259
  56. package/dist/cli/index.js.map +1 -1
  57. package/dist/config/budget-schema.cjs +172 -0
  58. package/dist/config/budget-schema.cjs.map +1 -0
  59. package/dist/config/budget-schema.d.cts +59 -0
  60. package/dist/config/budget-schema.d.cts.map +1 -0
  61. package/dist/config/budget-schema.d.ts +59 -0
  62. package/dist/config/budget-schema.d.ts.map +1 -0
  63. package/dist/config/budget-schema.js +166 -0
  64. package/dist/config/budget-schema.js.map +1 -0
  65. package/dist/config/schema.cjs +182 -2
  66. package/dist/config/schema.cjs.map +1 -1
  67. package/dist/config/schema.d.cts +122 -3
  68. package/dist/config/schema.d.cts.map +1 -1
  69. package/dist/config/schema.d.ts +122 -3
  70. package/dist/config/schema.d.ts.map +1 -1
  71. package/dist/config/schema.js +180 -1
  72. package/dist/config/schema.js.map +1 -1
  73. package/dist/constants.cjs +45 -2
  74. package/dist/constants.cjs.map +1 -1
  75. package/dist/constants.d.cts +41 -0
  76. package/dist/constants.d.cts.map +1 -1
  77. package/dist/constants.d.ts +41 -0
  78. package/dist/constants.d.ts.map +1 -1
  79. package/dist/constants.js +44 -1
  80. package/dist/constants.js.map +1 -1
  81. package/dist/core/engine.cjs +104 -15
  82. package/dist/core/engine.cjs.map +1 -1
  83. package/dist/core/engine.d.cts +7 -4
  84. package/dist/core/engine.d.cts.map +1 -1
  85. package/dist/core/engine.d.ts +7 -4
  86. package/dist/core/engine.d.ts.map +1 -1
  87. package/dist/core/engine.js +105 -16
  88. package/dist/core/engine.js.map +1 -1
  89. package/dist/core/output-path-resolver.cjs +41 -0
  90. package/dist/core/output-path-resolver.cjs.map +1 -0
  91. package/dist/core/output-path-resolver.d.cts +10 -0
  92. package/dist/core/output-path-resolver.d.cts.map +1 -0
  93. package/dist/core/output-path-resolver.d.ts +10 -0
  94. package/dist/core/output-path-resolver.d.ts.map +1 -0
  95. package/dist/core/output-path-resolver.js +37 -0
  96. package/dist/core/output-path-resolver.js.map +1 -0
  97. package/dist/errors/base.cjs +12 -3
  98. package/dist/errors/base.cjs.map +1 -1
  99. package/dist/errors/base.d.cts +7 -0
  100. package/dist/errors/base.d.cts.map +1 -1
  101. package/dist/errors/base.d.ts +7 -0
  102. package/dist/errors/base.d.ts.map +1 -1
  103. package/dist/errors/base.js +10 -2
  104. package/dist/errors/base.js.map +1 -1
  105. package/dist/errors/budget.cjs +37 -0
  106. package/dist/errors/budget.cjs.map +1 -0
  107. package/dist/errors/budget.d.cts +31 -0
  108. package/dist/errors/budget.d.cts.map +1 -0
  109. package/dist/errors/budget.d.ts +31 -0
  110. package/dist/errors/budget.d.ts.map +1 -0
  111. package/dist/errors/budget.js +33 -0
  112. package/dist/errors/budget.js.map +1 -0
  113. package/dist/errors/index.cjs +4 -1
  114. package/dist/errors/index.cjs.map +1 -1
  115. package/dist/errors/index.d.cts +1 -0
  116. package/dist/errors/index.d.cts.map +1 -1
  117. package/dist/errors/index.d.ts +1 -0
  118. package/dist/errors/index.d.ts.map +1 -1
  119. package/dist/errors/index.js +2 -0
  120. package/dist/errors/index.js.map +1 -1
  121. package/dist/formatters/history/base.cjs +9 -0
  122. package/dist/formatters/history/base.cjs.map +1 -0
  123. package/dist/formatters/history/base.d.cts +26 -0
  124. package/dist/formatters/history/base.d.cts.map +1 -0
  125. package/dist/formatters/history/base.d.ts +26 -0
  126. package/dist/formatters/history/base.d.ts.map +1 -0
  127. package/dist/formatters/history/base.js +8 -0
  128. package/dist/formatters/history/base.js.map +1 -0
  129. package/dist/formatters/history/compare.cjs +127 -0
  130. package/dist/formatters/history/compare.cjs.map +1 -0
  131. package/dist/formatters/history/compare.d.cts +21 -0
  132. package/dist/formatters/history/compare.d.cts.map +1 -0
  133. package/dist/formatters/history/compare.d.ts +21 -0
  134. package/dist/formatters/history/compare.d.ts.map +1 -0
  135. package/dist/formatters/history/compare.js +123 -0
  136. package/dist/formatters/history/compare.js.map +1 -0
  137. package/dist/formatters/history/list.cjs +74 -0
  138. package/dist/formatters/history/list.cjs.map +1 -0
  139. package/dist/formatters/history/list.d.cts +25 -0
  140. package/dist/formatters/history/list.d.cts.map +1 -0
  141. package/dist/formatters/history/list.d.ts +25 -0
  142. package/dist/formatters/history/list.d.ts.map +1 -0
  143. package/dist/formatters/history/list.js +70 -0
  144. package/dist/formatters/history/list.js.map +1 -0
  145. package/dist/formatters/history/show.cjs +98 -0
  146. package/dist/formatters/history/show.cjs.map +1 -0
  147. package/dist/formatters/history/show.d.cts +21 -0
  148. package/dist/formatters/history/show.d.cts.map +1 -0
  149. package/dist/formatters/history/show.d.ts +21 -0
  150. package/dist/formatters/history/show.d.ts.map +1 -0
  151. package/dist/formatters/history/show.js +94 -0
  152. package/dist/formatters/history/show.js.map +1 -0
  153. package/dist/formatters/history/trends.cjs +194 -0
  154. package/dist/formatters/history/trends.cjs.map +1 -0
  155. package/dist/formatters/history/trends.d.cts +22 -0
  156. package/dist/formatters/history/trends.d.cts.map +1 -0
  157. package/dist/formatters/history/trends.d.ts +22 -0
  158. package/dist/formatters/history/trends.d.ts.map +1 -0
  159. package/dist/formatters/history/trends.js +190 -0
  160. package/dist/formatters/history/trends.js.map +1 -0
  161. package/dist/formatters/history/visualization.cjs +79 -0
  162. package/dist/formatters/history/visualization.cjs.map +1 -0
  163. package/dist/formatters/history/visualization.d.cts +24 -0
  164. package/dist/formatters/history/visualization.d.cts.map +1 -0
  165. package/dist/formatters/history/visualization.d.ts +24 -0
  166. package/dist/formatters/history/visualization.d.ts.map +1 -0
  167. package/dist/formatters/history/visualization.js +74 -0
  168. package/dist/formatters/history/visualization.js.map +1 -0
  169. package/dist/index.cjs +27 -17
  170. package/dist/index.cjs.map +1 -1
  171. package/dist/index.d.cts +10 -5
  172. package/dist/index.d.cts.map +1 -1
  173. package/dist/index.d.ts +10 -5
  174. package/dist/index.d.ts.map +1 -1
  175. package/dist/index.js +14 -9
  176. package/dist/index.js.map +1 -1
  177. package/dist/reporters/csv.cjs +39 -19
  178. package/dist/reporters/csv.cjs.map +1 -1
  179. package/dist/reporters/csv.d.cts +4 -7
  180. package/dist/reporters/csv.d.cts.map +1 -1
  181. package/dist/reporters/csv.d.ts +4 -7
  182. package/dist/reporters/csv.d.ts.map +1 -1
  183. package/dist/reporters/csv.js +38 -18
  184. package/dist/reporters/csv.js.map +1 -1
  185. package/dist/reporters/human.cjs +87 -99
  186. package/dist/reporters/human.cjs.map +1 -1
  187. package/dist/reporters/human.d.cts +15 -14
  188. package/dist/reporters/human.d.cts.map +1 -1
  189. package/dist/reporters/human.d.ts +15 -14
  190. package/dist/reporters/human.d.ts.map +1 -1
  191. package/dist/reporters/human.js +68 -80
  192. package/dist/reporters/human.js.map +1 -1
  193. package/dist/reporters/json.cjs +25 -50
  194. package/dist/reporters/json.cjs.map +1 -1
  195. package/dist/reporters/json.d.cts +3 -29
  196. package/dist/reporters/json.d.cts.map +1 -1
  197. package/dist/reporters/json.d.ts +3 -29
  198. package/dist/reporters/json.d.ts.map +1 -1
  199. package/dist/reporters/json.js +26 -51
  200. package/dist/reporters/json.js.map +1 -1
  201. package/dist/reporters/profile-human.cjs +149 -0
  202. package/dist/reporters/profile-human.cjs.map +1 -0
  203. package/dist/reporters/profile-human.d.cts +44 -0
  204. package/dist/reporters/profile-human.d.cts.map +1 -0
  205. package/dist/reporters/profile-human.d.ts +44 -0
  206. package/dist/reporters/profile-human.d.ts.map +1 -0
  207. package/dist/reporters/profile-human.js +142 -0
  208. package/dist/reporters/profile-human.js.map +1 -0
  209. package/dist/reporters/simple.cjs +66 -46
  210. package/dist/reporters/simple.cjs.map +1 -1
  211. package/dist/reporters/simple.d.cts +15 -15
  212. package/dist/reporters/simple.d.cts.map +1 -1
  213. package/dist/reporters/simple.d.ts +15 -15
  214. package/dist/reporters/simple.d.ts.map +1 -1
  215. package/dist/reporters/simple.js +65 -45
  216. package/dist/reporters/simple.js.map +1 -1
  217. package/dist/schema/modestbench-config.schema.json +153 -0
  218. package/dist/services/baseline-storage.cjs +151 -0
  219. package/dist/services/baseline-storage.cjs.map +1 -0
  220. package/dist/services/baseline-storage.d.cts +55 -0
  221. package/dist/services/baseline-storage.d.cts.map +1 -0
  222. package/dist/services/baseline-storage.d.ts +55 -0
  223. package/dist/services/baseline-storage.d.ts.map +1 -0
  224. package/dist/services/baseline-storage.js +147 -0
  225. package/dist/services/baseline-storage.js.map +1 -0
  226. package/dist/services/budget-evaluator.cjs +146 -0
  227. package/dist/services/budget-evaluator.cjs.map +1 -0
  228. package/dist/services/budget-evaluator.d.cts +29 -0
  229. package/dist/services/budget-evaluator.d.cts.map +1 -0
  230. package/dist/services/budget-evaluator.d.ts +29 -0
  231. package/dist/services/budget-evaluator.d.ts.map +1 -0
  232. package/dist/services/budget-evaluator.js +142 -0
  233. package/dist/services/budget-evaluator.js.map +1 -0
  234. package/dist/{config/manager.cjs → services/config-manager.cjs} +25 -11
  235. package/dist/services/config-manager.cjs.map +1 -0
  236. package/dist/{config/manager.d.cts → services/config-manager.d.cts} +7 -2
  237. package/dist/services/config-manager.d.cts.map +1 -0
  238. package/dist/{config/manager.d.ts → services/config-manager.d.ts} +7 -2
  239. package/dist/services/config-manager.d.ts.map +1 -0
  240. package/dist/{config/manager.js → services/config-manager.js} +25 -11
  241. package/dist/services/config-manager.js.map +1 -0
  242. package/dist/{core/loader.cjs → services/file-loader.cjs} +5 -8
  243. package/dist/services/file-loader.cjs.map +1 -0
  244. package/dist/{core/loader.d.cts → services/file-loader.d.cts} +1 -1
  245. package/dist/services/file-loader.d.cts.map +1 -0
  246. package/dist/{core/loader.d.ts → services/file-loader.d.ts} +1 -1
  247. package/dist/services/file-loader.d.ts.map +1 -0
  248. package/dist/{core/loader.js → services/file-loader.js} +5 -8
  249. package/dist/services/file-loader.js.map +1 -0
  250. package/dist/services/history/comparison.cjs +124 -0
  251. package/dist/services/history/comparison.cjs.map +1 -0
  252. package/dist/services/history/comparison.d.cts +18 -0
  253. package/dist/services/history/comparison.d.cts.map +1 -0
  254. package/dist/services/history/comparison.d.ts +18 -0
  255. package/dist/services/history/comparison.d.ts.map +1 -0
  256. package/dist/services/history/comparison.js +120 -0
  257. package/dist/services/history/comparison.js.map +1 -0
  258. package/dist/services/history/models.cjs +9 -0
  259. package/dist/services/history/models.cjs.map +1 -0
  260. package/dist/services/history/models.d.cts +139 -0
  261. package/dist/services/history/models.d.cts.map +1 -0
  262. package/dist/services/history/models.d.ts +139 -0
  263. package/dist/services/history/models.d.ts.map +1 -0
  264. package/dist/services/history/models.js +8 -0
  265. package/dist/services/history/models.js.map +1 -0
  266. package/dist/services/history/query.cjs +97 -0
  267. package/dist/services/history/query.cjs.map +1 -0
  268. package/dist/services/history/query.d.cts +38 -0
  269. package/dist/services/history/query.d.cts.map +1 -0
  270. package/dist/services/history/query.d.ts +38 -0
  271. package/dist/services/history/query.d.ts.map +1 -0
  272. package/dist/services/history/query.js +92 -0
  273. package/dist/services/history/query.js.map +1 -0
  274. package/dist/services/history/trend-analysis.cjs +187 -0
  275. package/dist/services/history/trend-analysis.cjs.map +1 -0
  276. package/dist/services/history/trend-analysis.d.cts +34 -0
  277. package/dist/services/history/trend-analysis.d.cts.map +1 -0
  278. package/dist/services/history/trend-analysis.d.ts +34 -0
  279. package/dist/services/history/trend-analysis.d.ts.map +1 -0
  280. package/dist/services/history/trend-analysis.js +179 -0
  281. package/dist/services/history/trend-analysis.js.map +1 -0
  282. package/dist/{storage/history.cjs → services/history-storage.cjs} +1 -1
  283. package/dist/services/history-storage.cjs.map +1 -0
  284. package/dist/{storage/history.d.cts → services/history-storage.d.cts} +1 -1
  285. package/dist/services/history-storage.d.cts.map +1 -0
  286. package/dist/{storage/history.d.ts → services/history-storage.d.ts} +1 -1
  287. package/dist/services/history-storage.d.ts.map +1 -0
  288. package/dist/{storage/history.js → services/history-storage.js} +1 -1
  289. package/dist/services/history-storage.js.map +1 -0
  290. package/dist/services/profiler/profile-filter.cjs +113 -0
  291. package/dist/services/profiler/profile-filter.cjs.map +1 -0
  292. package/dist/services/profiler/profile-filter.d.cts +20 -0
  293. package/dist/services/profiler/profile-filter.d.cts.map +1 -0
  294. package/dist/services/profiler/profile-filter.d.ts +20 -0
  295. package/dist/services/profiler/profile-filter.d.ts.map +1 -0
  296. package/dist/services/profiler/profile-filter.js +109 -0
  297. package/dist/services/profiler/profile-filter.js.map +1 -0
  298. package/dist/services/profiler/profile-parser.cjs +139 -0
  299. package/dist/services/profiler/profile-parser.cjs.map +1 -0
  300. package/dist/services/profiler/profile-parser.d.cts +18 -0
  301. package/dist/services/profiler/profile-parser.d.cts.map +1 -0
  302. package/dist/services/profiler/profile-parser.d.ts +18 -0
  303. package/dist/services/profiler/profile-parser.d.ts.map +1 -0
  304. package/dist/services/profiler/profile-parser.js +132 -0
  305. package/dist/services/profiler/profile-parser.js.map +1 -0
  306. package/dist/services/profiler/profile-runner.cjs +90 -0
  307. package/dist/services/profiler/profile-runner.cjs.map +1 -0
  308. package/dist/services/profiler/profile-runner.d.cts +29 -0
  309. package/dist/services/profiler/profile-runner.d.cts.map +1 -0
  310. package/dist/services/profiler/profile-runner.d.ts +29 -0
  311. package/dist/services/profiler/profile-runner.d.ts.map +1 -0
  312. package/dist/services/profiler/profile-runner.js +86 -0
  313. package/dist/services/profiler/profile-runner.js.map +1 -0
  314. package/dist/{progress/manager.cjs → services/progress-manager.cjs} +1 -1
  315. package/dist/services/progress-manager.cjs.map +1 -0
  316. package/dist/{progress/manager.d.cts → services/progress-manager.d.cts} +1 -1
  317. package/dist/services/progress-manager.d.cts.map +1 -0
  318. package/dist/{progress/manager.d.ts → services/progress-manager.d.ts} +1 -1
  319. package/dist/services/progress-manager.d.ts.map +1 -0
  320. package/dist/{progress/manager.js → services/progress-manager.js} +1 -1
  321. package/dist/services/progress-manager.js.map +1 -0
  322. package/dist/{reporters/registry.cjs → services/reporter-registry.cjs} +19 -25
  323. package/dist/services/reporter-registry.cjs.map +1 -0
  324. package/dist/{reporters/registry.d.cts → services/reporter-registry.d.cts} +19 -41
  325. package/dist/services/reporter-registry.d.cts.map +1 -0
  326. package/dist/{reporters/registry.d.ts → services/reporter-registry.d.ts} +19 -41
  327. package/dist/services/reporter-registry.d.ts.map +1 -0
  328. package/dist/{reporters/registry.js → services/reporter-registry.js} +19 -25
  329. package/dist/services/reporter-registry.js.map +1 -0
  330. package/dist/types/budgets.cjs +8 -0
  331. package/dist/types/budgets.cjs.map +1 -0
  332. package/dist/types/budgets.d.cts +149 -0
  333. package/dist/types/budgets.d.cts.map +1 -0
  334. package/dist/types/budgets.d.ts +149 -0
  335. package/dist/types/budgets.d.ts.map +1 -0
  336. package/dist/types/budgets.js +7 -0
  337. package/dist/types/budgets.js.map +1 -0
  338. package/dist/types/cli.cjs +2 -11
  339. package/dist/types/cli.cjs.map +1 -1
  340. package/dist/types/cli.d.cts +3 -224
  341. package/dist/types/cli.d.cts.map +1 -1
  342. package/dist/types/cli.d.ts +3 -224
  343. package/dist/types/cli.d.ts.map +1 -1
  344. package/dist/types/cli.js +2 -11
  345. package/dist/types/cli.js.map +1 -1
  346. package/dist/types/core.cjs +6 -1
  347. package/dist/types/core.cjs.map +1 -1
  348. package/dist/types/core.d.cts +13 -2
  349. package/dist/types/core.d.cts.map +1 -1
  350. package/dist/types/core.d.ts +13 -2
  351. package/dist/types/core.d.ts.map +1 -1
  352. package/dist/types/core.js +2 -1
  353. package/dist/types/core.js.map +1 -1
  354. package/dist/types/index.cjs +5 -0
  355. package/dist/types/index.cjs.map +1 -1
  356. package/dist/types/index.d.cts +2 -0
  357. package/dist/types/index.d.cts.map +1 -1
  358. package/dist/types/index.d.ts +2 -0
  359. package/dist/types/index.d.ts.map +1 -1
  360. package/dist/types/index.js +2 -0
  361. package/dist/types/index.js.map +1 -1
  362. package/dist/types/interfaces.d.cts +15 -8
  363. package/dist/types/interfaces.d.cts.map +1 -1
  364. package/dist/types/interfaces.d.ts +15 -8
  365. package/dist/types/interfaces.d.ts.map +1 -1
  366. package/dist/types/profiler.cjs +11 -0
  367. package/dist/types/profiler.cjs.map +1 -0
  368. package/dist/types/profiler.d.cts +100 -0
  369. package/dist/types/profiler.d.cts.map +1 -0
  370. package/dist/types/profiler.d.ts +100 -0
  371. package/dist/types/profiler.d.ts.map +1 -0
  372. package/dist/types/profiler.js +10 -0
  373. package/dist/types/profiler.js.map +1 -0
  374. package/dist/types/utility.cjs.map +1 -1
  375. package/dist/types/utility.d.cts +0 -8
  376. package/dist/types/utility.d.cts.map +1 -1
  377. package/dist/types/utility.d.ts +0 -8
  378. package/dist/types/utility.d.ts.map +1 -1
  379. package/dist/types/utility.js.map +1 -1
  380. package/dist/utils/ansi.cjs +61 -0
  381. package/dist/utils/ansi.cjs.map +1 -0
  382. package/dist/utils/ansi.d.cts +53 -0
  383. package/dist/utils/ansi.d.cts.map +1 -0
  384. package/dist/utils/ansi.d.ts +53 -0
  385. package/dist/utils/ansi.d.ts.map +1 -0
  386. package/dist/utils/ansi.js +57 -0
  387. package/dist/utils/ansi.js.map +1 -0
  388. package/dist/utils/identifiers.cjs +32 -0
  389. package/dist/utils/identifiers.cjs.map +1 -0
  390. package/dist/utils/identifiers.d.cts +32 -0
  391. package/dist/utils/identifiers.d.cts.map +1 -0
  392. package/dist/utils/identifiers.d.ts +32 -0
  393. package/dist/utils/identifiers.d.ts.map +1 -0
  394. package/dist/utils/identifiers.js +27 -0
  395. package/dist/utils/identifiers.js.map +1 -0
  396. package/dist/utils/package.cjs +40 -0
  397. package/dist/utils/package.cjs.map +1 -0
  398. package/dist/utils/package.d.cts +15 -0
  399. package/dist/utils/package.d.cts.map +1 -0
  400. package/dist/utils/package.d.ts +15 -0
  401. package/dist/utils/package.d.ts.map +1 -0
  402. package/dist/utils/package.js +33 -0
  403. package/dist/utils/package.js.map +1 -0
  404. package/dist/utils/type-guards.cjs +48 -0
  405. package/dist/utils/type-guards.cjs.map +1 -0
  406. package/dist/utils/type-guards.d.cts +22 -0
  407. package/dist/utils/type-guards.d.cts.map +1 -0
  408. package/dist/utils/type-guards.d.ts +22 -0
  409. package/dist/utils/type-guards.d.ts.map +1 -0
  410. package/dist/utils/type-guards.js +43 -0
  411. package/dist/utils/type-guards.js.map +1 -0
  412. package/package.json +14 -13
  413. package/src/bootstrap.ts +5 -5
  414. package/src/cli/commands/analyze.ts +101 -0
  415. package/src/cli/commands/baseline.ts +577 -0
  416. package/src/cli/commands/history.ts +194 -342
  417. package/src/cli/commands/init.ts +105 -183
  418. package/src/cli/commands/run.ts +186 -88
  419. package/src/cli/index.ts +731 -234
  420. package/src/config/budget-schema.ts +189 -0
  421. package/src/config/schema.ts +260 -1
  422. package/src/constants.ts +53 -1
  423. package/src/core/engine.ts +153 -14
  424. package/src/core/output-path-resolver.ts +46 -0
  425. package/src/errors/base.ts +11 -2
  426. package/src/errors/budget.ts +38 -0
  427. package/src/errors/index.ts +3 -0
  428. package/src/formatters/history/base.ts +28 -0
  429. package/src/formatters/history/compare.ts +186 -0
  430. package/src/formatters/history/list.ts +101 -0
  431. package/src/formatters/history/show.ts +155 -0
  432. package/src/formatters/history/trends.ts +281 -0
  433. package/src/formatters/history/visualization.ts +93 -0
  434. package/src/index.ts +17 -12
  435. package/src/reporters/csv.ts +55 -26
  436. package/src/reporters/human.ts +90 -89
  437. package/src/reporters/json.ts +27 -72
  438. package/src/reporters/profile-human.ts +204 -0
  439. package/src/reporters/simple.ts +85 -54
  440. package/src/services/baseline-storage.ts +199 -0
  441. package/src/services/budget-evaluator.ts +182 -0
  442. package/src/{config/manager.ts → services/config-manager.ts} +24 -9
  443. package/src/{core/loader.ts → services/file-loader.ts} +4 -7
  444. package/src/services/history/comparison.ts +130 -0
  445. package/src/services/history/models.ts +148 -0
  446. package/src/services/history/query.ts +116 -0
  447. package/src/services/history/trend-analysis.ts +238 -0
  448. package/src/services/profiler/profile-filter.ts +143 -0
  449. package/src/services/profiler/profile-parser.ts +194 -0
  450. package/src/services/profiler/profile-runner.ts +121 -0
  451. package/src/{reporters/registry.ts → services/reporter-registry.ts} +46 -81
  452. package/src/types/budgets.ts +180 -0
  453. package/src/types/cli.ts +5 -235
  454. package/src/types/core.ts +50 -10
  455. package/src/types/index.ts +5 -0
  456. package/src/types/interfaces.ts +16 -6
  457. package/src/types/profiler.ts +132 -0
  458. package/src/types/utility.ts +0 -10
  459. package/src/utils/ansi.ts +59 -0
  460. package/src/utils/identifiers.ts +58 -0
  461. package/src/utils/package.ts +35 -0
  462. package/src/utils/type-guards.ts +51 -0
  463. package/dist/config/manager.cjs.map +0 -1
  464. package/dist/config/manager.d.cts.map +0 -1
  465. package/dist/config/manager.d.ts.map +0 -1
  466. package/dist/config/manager.js.map +0 -1
  467. package/dist/core/loader.cjs.map +0 -1
  468. package/dist/core/loader.d.cts.map +0 -1
  469. package/dist/core/loader.d.ts.map +0 -1
  470. package/dist/core/loader.js.map +0 -1
  471. package/dist/progress/manager.cjs.map +0 -1
  472. package/dist/progress/manager.d.cts.map +0 -1
  473. package/dist/progress/manager.d.ts.map +0 -1
  474. package/dist/progress/manager.js.map +0 -1
  475. package/dist/reporters/registry.cjs.map +0 -1
  476. package/dist/reporters/registry.d.cts.map +0 -1
  477. package/dist/reporters/registry.d.ts.map +0 -1
  478. package/dist/reporters/registry.js.map +0 -1
  479. package/dist/storage/history.cjs.map +0 -1
  480. package/dist/storage/history.d.cts.map +0 -1
  481. package/dist/storage/history.d.ts.map +0 -1
  482. package/dist/storage/history.js.map +0 -1
  483. /package/src/{storage/history.ts → services/history-storage.ts} +0 -0
  484. /package/src/{progress/manager.ts → services/progress-manager.ts} +0 -0
package/src/cli/index.ts CHANGED
@@ -7,8 +7,6 @@
7
7
  * global options, help generation, and dependency injection setup.
8
8
  */
9
9
 
10
- import type { Argv } from 'yargs';
11
-
12
10
  import { realpathSync } from 'node:fs';
13
11
  import { fileURLToPath } from 'node:url';
14
12
  import yargs from 'yargs';
@@ -17,13 +15,24 @@ import { hideBin } from 'yargs/helpers';
17
15
  import type {
18
16
  BenchmarkEngine,
19
17
  ConfigurationManager,
18
+ Engine,
20
19
  HistoryStorage,
21
20
  ProgressManager,
22
21
  ReporterRegistry,
23
22
  } from '../types/index.js';
24
23
 
25
24
  import { bootstrap } from '../bootstrap.js';
25
+ import {
26
+ ABORT_TIMEOUT,
27
+ DEFAULT_ENGINE,
28
+ DEFAULT_REPORTER,
29
+ Engines,
30
+ ErrorCodes,
31
+ ExitCodes,
32
+ Reporters,
33
+ } from '../constants.js';
26
34
  import { AccurateEngine, TinybenchEngine } from '../core/engines/index.js';
35
+ import { isError } from '../errors/base.js';
27
36
  import { isModestBenchError, UnknownError } from '../errors/index.js';
28
37
  import {
29
38
  CsvReporter,
@@ -32,9 +41,30 @@ import {
32
41
  SimpleReporter,
33
42
  } from '../reporters/index.js';
34
43
  // Import commands
35
- import { handleHistoryCommand as historyCommand } from './commands/history.js';
44
+ import {
45
+ handleAnalyzeCommand as analyzeCommand,
46
+ type AnalyzeOptions,
47
+ } from './commands/analyze.js';
48
+ import {
49
+ handleAnalyzeCommand as handleBaselineAnalyzeCommand,
50
+ handleDeleteCommand as handleBaselineDeleteCommand,
51
+ handleListCommand as handleBaselineListCommand,
52
+ handleSetCommand as handleBaselineSetCommand,
53
+ handleShowCommand as handleBaselineShowCommand,
54
+ } from './commands/baseline.js';
55
+ import {
56
+ handleCleanCommand,
57
+ handleCompareCommand,
58
+ handleExportCommand,
59
+ handleListCommand,
60
+ handleShowCommand,
61
+ handleTrendsCommand,
62
+ } from './commands/history.js';
36
63
  import { handleInitCommand as initCommand } from './commands/init.js';
37
- import { handleRunCommand as runCommand } from './commands/run.js';
64
+ import {
65
+ RUN_COMMAND_DEFAULTS,
66
+ handleRunCommand as runCommand,
67
+ } from './commands/run.js';
38
68
 
39
69
  /**
40
70
  * CLI context with initialized services
@@ -56,28 +86,15 @@ interface GlobalOptions {
56
86
  /** Configuration file path */
57
87
  config?: string | undefined;
58
88
  /** Working directory */
59
- cwd: string;
89
+ cwd?: string;
60
90
  /** JSON output for machine parsing */
61
- json: boolean;
91
+ json?: boolean;
62
92
  /** Disable colored output */
63
- noColor: boolean;
93
+ noColor?: boolean;
64
94
  /** Enable verbose output */
65
- verbose: boolean;
95
+ verbose?: boolean;
66
96
  }
67
97
 
68
- /**
69
- * Exit codes for the CLI
70
- */
71
- export const ExitCodes = {
72
- BENCHMARK_FAILURES: 1,
73
- CONFIG_ERROR: 2,
74
- DISCOVERY_ERROR: 3,
75
- RUNTIME_ERROR: 5,
76
- SUCCESS: 0,
77
- UNKNOWN_ERROR: 99,
78
- VALIDATION_ERROR: 4,
79
- } as const;
80
-
81
98
  /**
82
99
  * Initialize and run the CLI
83
100
  */
@@ -103,7 +120,9 @@ export const main = async (
103
120
  const cli = yargs(args);
104
121
 
105
122
  // Configure global options and commands
123
+
106
124
  await cli
125
+ .scriptName('modestbench')
107
126
  .option('config', {
108
127
  alias: 'c',
109
128
  description: 'Path to configuration file',
@@ -112,33 +131,34 @@ export const main = async (
112
131
  })
113
132
  .option('verbose', {
114
133
  alias: 'v',
115
- default: false,
134
+ defaultDescription: String(RUN_COMMAND_DEFAULTS.verbose),
116
135
  description: 'Enable verbose output',
117
136
  global: true,
118
137
  type: 'boolean',
119
138
  })
120
139
  .option('no-color', {
121
- default: false,
140
+ defaultDescription: 'false',
122
141
  description: 'Disable colored output',
123
142
  global: true,
124
143
  type: 'boolean',
125
144
  })
126
145
  .option('progress', {
127
- default: true,
146
+ defaultDescription: 'true',
128
147
  description: 'Show animated progress bar',
129
148
  global: true,
130
149
  type: 'boolean',
131
150
  })
132
151
  .option('json', {
133
- default: false,
152
+ defaultDescription: 'false',
134
153
  description: 'Output results in JSON format',
135
154
  global: true,
136
155
  type: 'boolean',
137
156
  })
138
157
  .option('cwd', {
139
- default: process.cwd(),
158
+ defaultDescription: '.',
140
159
  description: 'Working directory',
141
160
  global: true,
161
+ normalize: true,
142
162
  type: 'string',
143
163
  })
144
164
  .help()
@@ -146,18 +166,18 @@ export const main = async (
146
166
  .version()
147
167
  .alias('version', 'V')
148
168
  .strict()
149
- .demandCommand(1, 'You must specify a command')
169
+ .demandCommand(1)
150
170
  .recommendCommands()
151
171
  .completion()
152
172
  .wrap(Math.min(120, cli.terminalWidth()))
153
173
  .command(
154
174
  ['$0 [pattern..]', 'run [pattern..]'],
155
175
  'Run benchmark files',
156
- (yargs) => {
157
- return yargs
176
+ (yargs) =>
177
+ yargs
158
178
  .positional('pattern', {
159
179
  array: true,
160
- default: ['./bench/**/*.bench.{js,mjs,cjs,ts}'],
180
+ defaultDescription: '(auto-discovered from bench/ directory)',
161
181
  describe:
162
182
  'File paths, directory paths, or glob patterns for benchmark files',
163
183
  type: 'string',
@@ -167,26 +187,26 @@ export const main = async (
167
187
  description: 'Path to configuration file',
168
188
  type: 'string',
169
189
  })
170
- .option('reporters', {
190
+ .option('reporter', {
171
191
  alias: 'r',
172
- coerce: (value: string | string[]) => {
173
- // Handle comma-separated values
174
- if (Array.isArray(value)) {
175
- return value.flatMap((v) =>
176
- v.split(',').map((s) => s.trim()),
177
- );
178
- }
179
- return value.split(',').map((s) => s.trim());
180
- },
181
- default: ['human'],
192
+ array: true,
193
+ choices: Object.values(Reporters).sort(),
194
+ defaultDescription: DEFAULT_REPORTER,
182
195
  description: 'Output reporters to use (human,json,csv)',
183
- type: 'array',
196
+ type: 'string',
184
197
  })
185
198
  .option('output', {
186
199
  alias: 'o',
187
200
  description: 'Output directory for reports',
188
201
  type: 'string',
189
202
  })
203
+ .option('output-file', {
204
+ alias: ['of', 'file'],
205
+ description:
206
+ 'Custom filename for reporter output (use with single reporter only)',
207
+ requiresArg: true,
208
+ type: 'string',
209
+ })
190
210
  .option('iterations', {
191
211
  alias: 'i',
192
212
  description: 'Number of iterations per benchmark',
@@ -198,11 +218,12 @@ export const main = async (
198
218
  type: 'number',
199
219
  })
200
220
  .option('warmup', {
201
- alias: 'w',
221
+ alias: ['w', 'warm'],
202
222
  description: 'Number of warmup iterations',
203
223
  type: 'number',
204
224
  })
205
225
  .option('limit-by', {
226
+ alias: ['l', 'limit'],
206
227
  choices: ['time', 'iterations', 'any', 'all'],
207
228
  description:
208
229
  'How to limit benchmarks: time (time budget), iterations (sample count), any (either threshold), all (both thresholds)',
@@ -210,22 +231,15 @@ export const main = async (
210
231
  })
211
232
  .option('bail', {
212
233
  alias: 'b',
213
- default: false,
234
+ defaultDescription: String(RUN_COMMAND_DEFAULTS.bail),
214
235
  description: 'Stop on first failure',
215
236
  type: 'boolean',
216
237
  })
217
238
  .option('exclude', {
218
- coerce: (value: string | string[]) => {
219
- // Handle comma-separated values
220
- if (Array.isArray(value)) {
221
- return value.flatMap((v) =>
222
- v.split(',').map((s) => s.trim()),
223
- );
224
- }
225
- return value.split(',').map((s) => s.trim());
226
- },
239
+ alias: 'X',
240
+ array: true,
227
241
  description: 'Exclude patterns (comma-separated)',
228
- type: 'array',
242
+ type: 'string',
229
243
  })
230
244
  .option('timeout', {
231
245
  description: 'Timeout per benchmark in milliseconds',
@@ -233,40 +247,25 @@ export const main = async (
233
247
  })
234
248
  .option('quiet', {
235
249
  alias: 'q',
236
- default: false,
250
+ defaultDescription: String(RUN_COMMAND_DEFAULTS.quiet),
237
251
  description: 'Minimal output',
238
252
  type: 'boolean',
239
253
  })
240
- .option('tags', {
241
- coerce: (value: string | string[]) => {
242
- // Handle comma-separated values
243
- if (Array.isArray(value)) {
244
- return value.flatMap((v) =>
245
- v.split(',').map((s) => s.trim()),
246
- );
247
- }
248
- return value.split(',').map((s) => s.trim());
249
- },
254
+ .option('tag', {
255
+ array: true,
250
256
  description: 'Include only benchmarks with any of these tags',
251
- type: 'array',
257
+ type: 'string',
252
258
  })
253
- .option('exclude-tags', {
254
- coerce: (value: string | string[]) => {
255
- // Handle comma-separated values
256
- if (Array.isArray(value)) {
257
- return value.flatMap((v) =>
258
- v.split(',').map((s) => s.trim()),
259
- );
260
- }
261
- return value.split(',').map((s) => s.trim());
262
- },
259
+ .option('exclude-tag', {
260
+ alias: 'T',
261
+ array: true,
263
262
  description: 'Exclude benchmarks with any of these tags',
264
- type: 'array',
263
+ type: 'string',
265
264
  })
266
265
  .option('engine', {
267
266
  alias: 'e',
268
- choices: ['tinybench', 'accurate'] as const,
269
- default: 'tinybench' as const,
267
+ choices: Object.values(Engines),
268
+ defaultDescription: DEFAULT_ENGINE,
270
269
  description:
271
270
  'Benchmark engine: tinybench (default) or accurate (requires --allow-natives-syntax)',
272
271
  type: 'string',
@@ -278,12 +277,23 @@ export const main = async (
278
277
  ['$0 run "src/**/*.bench.js"', 'Run specific glob pattern'],
279
278
  ['$0 run file1.bench.js file2.bench.js', 'Run specific files'],
280
279
  ['$0 run benchmarks/ tests/perf/', 'Run multiple directories'],
281
- ['$0 run --reporters json,csv', 'Use multiple reporters'],
280
+ ['$0 run -r json -r csv', 'Use multiple reporters'],
282
281
  ['$0 run --iterations 1000', 'Set iteration count'],
283
282
  ['$0 run --engine accurate', 'Use high-accuracy engine'],
284
283
  ['$0 run --bail', 'Stop on first failure'],
285
- ]);
286
- },
284
+ ])
285
+ .check((argv) => {
286
+ if (
287
+ argv.reporter &&
288
+ argv.reporter.length > 1 &&
289
+ argv['output-file']
290
+ ) {
291
+ throw new Error(
292
+ '--output-file can only be used with a single reporter. Use --output <dir> for multiple reporters.',
293
+ );
294
+ }
295
+ return true;
296
+ }),
287
297
  async (argv) => {
288
298
  const context = await createCliContext(
289
299
  argv,
@@ -296,16 +306,17 @@ export const main = async (
296
306
  cwd: argv.cwd,
297
307
  engine: argv.engine,
298
308
  exclude: argv.exclude,
299
- excludeTags: argv['exclude-tags'],
309
+ excludeTags: argv['exclude-tag'],
300
310
  iterations: argv.iterations,
301
311
  json: argv.json,
302
312
  noColor: argv.noColor,
303
313
  outputDir: argv.output,
314
+ outputFile: argv['output-file'],
304
315
  pattern: argv.pattern,
305
316
  progress: argv.progress,
306
317
  quiet: argv.quiet,
307
- reporters: argv.reporters,
308
- tags: argv.tags,
318
+ reporters: argv.reporter,
319
+ tags: argv.tag,
309
320
  time: argv.time,
310
321
  timeout: argv.timeout,
311
322
  verbose: argv.verbose,
@@ -314,111 +325,541 @@ export const main = async (
314
325
  process.exit(exitCode);
315
326
  },
316
327
  )
317
- .command(
318
- 'history <subcommand> [args..]',
319
- 'View and manage benchmark history',
320
- (yargs) => {
321
- return yargs
322
- .positional('subcommand', {
323
- choices: [
324
- 'list',
325
- 'show',
326
- 'compare',
327
- 'trends',
328
- 'clean',
329
- 'export',
330
- ] as const,
331
- demandOption: true,
332
- describe: 'History subcommand',
333
- type: 'string',
334
- })
335
- .positional('args', {
336
- array: true,
337
- describe: 'Additional arguments for the subcommand',
338
- type: 'string',
339
- })
340
- .option('since', {
341
- description:
342
- 'Show runs since date (ISO 8601 or relative like "1 week ago")',
343
- type: 'string',
344
- })
345
- .option('until', {
346
- description:
347
- 'Show runs until date (ISO 8601 or relative like "1 day ago")',
348
- type: 'string',
349
- })
350
- .option('pattern', {
351
- description: 'Filter by benchmark name pattern',
352
- type: 'string',
353
- })
354
- .option('tags', {
355
- description: 'Filter by tags',
356
- type: 'array',
357
- })
358
- .option('limit', {
359
- default: 10,
360
- description: 'Maximum number of results',
361
- type: 'number',
362
- })
363
- .option('format', {
364
- choices: ['human', 'json', 'csv'] as const,
365
- default: 'human' as const,
366
- description: 'Output format',
367
- type: 'string',
368
- })
369
- .option('maxAge', {
370
- description: 'Maximum age in days for cleanup',
371
- type: 'number',
372
- })
373
- .option('maxRuns', {
374
- description: 'Maximum number of runs to keep',
375
- type: 'number',
376
- })
377
- .option('maxSize', {
378
- description: 'Maximum storage size in bytes',
379
- type: 'number',
380
- })
381
- .option('confirm', {
382
- default: false,
383
- description: 'Confirm cleanup operations',
384
- type: 'boolean',
385
- })
386
- .option('output', {
387
- description: 'Output file path',
388
- type: 'string',
389
- })
390
- .example([
391
- ['$0 history list', 'List recent benchmark runs'],
392
- ['$0 history show <run-id>', 'Show detailed results for run'],
393
- ['$0 history compare <run-id1> <run-id2>', 'Compare two runs'],
394
- ['$0 history trends [pattern]', 'Show performance trends'],
395
- ['$0 history clean --max-runs 50', 'Keep only latest 50 runs'],
396
- ['$0 history export --format csv', 'Export to CSV'],
397
- ]);
398
- },
399
- async (argv) => {
400
- const context = await createCliContext(argv, abortController!);
401
- const exitCode = await historyCommand(context, {
402
- args: argv.args,
403
- confirm: argv.confirm,
404
- cwd: argv.cwd,
405
- format: argv.format,
406
- limit: argv.limit,
407
- maxAge: argv.maxAge,
408
- maxRuns: argv.maxRuns,
409
- maxSize: argv.maxSize,
410
- outputDir: argv.output,
411
- pattern: argv.pattern,
412
- quiet: Boolean(argv.quiet),
413
- since: argv.since,
414
- subcommand: argv.subcommand,
415
- tags: argv.tags as string[] | undefined,
416
- until: argv.until,
417
- verbose: argv.verbose,
418
- });
419
- process.exit(exitCode);
420
- },
328
+ .command('history', 'View and manage benchmark history', (yargs) =>
329
+ yargs
330
+ .command(
331
+ 'list',
332
+ 'List recent benchmark runs',
333
+ (yargs) =>
334
+ yargs
335
+ .option('since', {
336
+ description:
337
+ 'Show runs since date (ISO 8601 or relative like "1 week ago")',
338
+ type: 'string',
339
+ })
340
+ .option('until', {
341
+ description:
342
+ 'Show runs until date (ISO 8601 or relative like "1 day ago")',
343
+ type: 'string',
344
+ })
345
+ .option('pattern', {
346
+ description: 'Filter by benchmark name pattern',
347
+ type: 'string',
348
+ })
349
+ .option('tag', {
350
+ alias: 't',
351
+ array: true,
352
+ description: 'Filter by tags (comma-separated)',
353
+ type: 'string',
354
+ })
355
+ .option('limit', {
356
+ defaultDescription: '10',
357
+ description: 'Maximum number of results',
358
+ type: 'number',
359
+ })
360
+ .option('format', {
361
+ choices: ['human', 'json', 'csv'] as const,
362
+ defaultDescription: 'human' as const,
363
+ description: 'Output format',
364
+ type: 'string',
365
+ })
366
+ .example([
367
+ ['$0 history list', 'List recent benchmark runs'],
368
+ [
369
+ '$0 history list --since "1 week ago"',
370
+ 'List runs from last week',
371
+ ],
372
+ ['$0 history list --limit 20', 'List 20 most recent runs'],
373
+ ['$0 history list --format json', 'List runs in JSON format'],
374
+ ]),
375
+ async (argv) => {
376
+ const context = await createCliContext(argv, abortController!);
377
+ const exitCode = await handleListCommand(context, {
378
+ cwd: argv.cwd,
379
+ format: argv.format,
380
+ limit: argv.limit,
381
+ pattern: argv.pattern,
382
+ since: argv.since,
383
+ tags: argv.tag,
384
+ until: argv.until,
385
+ verbose: argv.verbose,
386
+ });
387
+ process.exit(exitCode);
388
+ },
389
+ )
390
+ .command(
391
+ 'show <run-id>',
392
+ 'Show detailed results for a specific run',
393
+ (yargs) =>
394
+ yargs
395
+ .positional('run-id', {
396
+ demandOption: true,
397
+ describe: 'ID of the benchmark run to show',
398
+ type: 'string',
399
+ })
400
+ .option('format', {
401
+ choices: ['human', 'json', 'csv'] as const,
402
+ defaultDescription: 'human' as const,
403
+ description: 'Output format',
404
+ type: 'string',
405
+ })
406
+ .example([
407
+ [
408
+ '$0 history show abc123',
409
+ 'Show detailed results for run abc123',
410
+ ],
411
+ [
412
+ '$0 history show abc123 --format json',
413
+ 'Show run in JSON format',
414
+ ],
415
+ ]),
416
+ async (argv) => {
417
+ const context = await createCliContext(argv, abortController!);
418
+ const exitCode = await handleShowCommand(context, {
419
+ cwd: argv.cwd,
420
+ format: argv.format,
421
+ runId: argv['run-id'],
422
+ verbose: argv.verbose,
423
+ });
424
+ process.exit(exitCode);
425
+ },
426
+ )
427
+ .command(
428
+ 'compare <run-id1> <run-id2>',
429
+ 'Compare two benchmark runs',
430
+ (yargs) =>
431
+ yargs
432
+ .positional('run-id1', {
433
+ demandOption: true,
434
+ describe: 'ID of the first benchmark run',
435
+ type: 'string',
436
+ })
437
+ .positional('run-id2', {
438
+ demandOption: true,
439
+ describe: 'ID of the second benchmark run',
440
+ type: 'string',
441
+ })
442
+ .option('format', {
443
+ choices: ['human', 'json'] as const,
444
+ defaultDescription: 'human' as const,
445
+ description: 'Output format',
446
+ type: 'string',
447
+ })
448
+ .example([
449
+ ['$0 history compare abc123 def456', 'Compare two runs'],
450
+ [
451
+ '$0 history compare abc123 def456 --format json',
452
+ 'Compare in JSON format',
453
+ ],
454
+ ]),
455
+ async (argv) => {
456
+ const context = await createCliContext(argv, abortController!);
457
+ const exitCode = await handleCompareCommand(context, {
458
+ cwd: argv.cwd,
459
+ format: argv.format,
460
+ runId1: argv['run-id1'],
461
+ runId2: argv['run-id2'],
462
+ verbose: argv.verbose,
463
+ });
464
+ process.exit(exitCode);
465
+ },
466
+ )
467
+ .command(
468
+ 'trends [pattern]',
469
+ 'Show performance trends over time',
470
+ (yargs) =>
471
+ yargs
472
+ .positional('pattern', {
473
+ describe: 'Filter by benchmark name pattern',
474
+ type: 'string',
475
+ })
476
+ .option('since', {
477
+ description:
478
+ 'Show trends since date (ISO 8601 or relative like "1 week ago")',
479
+ type: 'string',
480
+ })
481
+ .option('until', {
482
+ description:
483
+ 'Show trends until date (ISO 8601 or relative like "1 day ago")',
484
+ type: 'string',
485
+ })
486
+ .option('tag', {
487
+ alias: 't',
488
+ array: true,
489
+ description: 'Filter by tags (comma-separated)',
490
+ type: 'string',
491
+ })
492
+ .option('limit', {
493
+ description: 'Maximum number of runs to analyze',
494
+ type: 'number',
495
+ })
496
+ .option('all', {
497
+ alias: 'a',
498
+ defaultDescription: 'false',
499
+ description: 'Analyze all runs (ignore limit)',
500
+ type: 'boolean',
501
+ })
502
+ .option('format', {
503
+ choices: ['human', 'json'] as const,
504
+ defaultDescription: 'human' as const,
505
+ description: 'Output format',
506
+ type: 'string',
507
+ })
508
+ .example([
509
+ [
510
+ '$0 history trends',
511
+ 'Show performance trends for all benchmarks',
512
+ ],
513
+ [
514
+ '$0 history trends --since "1 month ago"',
515
+ 'Show trends from last month',
516
+ ],
517
+ [
518
+ '$0 history trends "array-*"',
519
+ 'Show trends for array benchmarks',
520
+ ],
521
+ [
522
+ '$0 history trends --format json',
523
+ 'Output trends in JSON format',
524
+ ],
525
+ ]),
526
+ async (argv) => {
527
+ const context = await createCliContext(argv, abortController!);
528
+ const exitCode = await handleTrendsCommand(context, {
529
+ all: argv.all,
530
+ cwd: argv.cwd,
531
+ format: argv.format,
532
+ limit: argv.limit,
533
+ pattern: argv.pattern,
534
+ since: argv.since,
535
+ tags: argv.tag,
536
+ until: argv.until,
537
+ verbose: argv.verbose,
538
+ });
539
+ process.exit(exitCode);
540
+ },
541
+ )
542
+ .command(
543
+ 'clean',
544
+ 'Clean up old benchmark history',
545
+ (yargs) =>
546
+ yargs
547
+ .option('max-age', {
548
+ description: 'Remove runs older than this many days',
549
+ type: 'number',
550
+ })
551
+ .option('max-runs', {
552
+ description: 'Keep only this many most recent runs',
553
+ type: 'number',
554
+ })
555
+ .option('max-size', {
556
+ description: 'Keep history under this size in bytes',
557
+ type: 'number',
558
+ })
559
+ .option('yes', {
560
+ alias: 'y',
561
+ description: 'Confirm cleanup without prompting',
562
+ type: 'boolean',
563
+ })
564
+ .option('quiet', {
565
+ default: false,
566
+ description: 'Minimal output',
567
+ type: 'boolean',
568
+ })
569
+ .check((argv) => {
570
+ if (
571
+ !argv['max-age'] &&
572
+ !argv['max-runs'] &&
573
+ !argv['max-size']
574
+ ) {
575
+ throw new Error(
576
+ 'At least one cleanup criterion must be specified (--max-age, --max-runs, or --max-size)',
577
+ );
578
+ }
579
+ return true;
580
+ })
581
+ .example([
582
+ [
583
+ '$0 history clean --max-runs 50 --yes',
584
+ 'Keep only latest 50 runs',
585
+ ],
586
+ [
587
+ '$0 history clean --max-age 30',
588
+ 'Preview removing runs older than 30 days',
589
+ ],
590
+ [
591
+ '$0 history clean --max-size 10485760',
592
+ 'Keep history under 10MB',
593
+ ],
594
+ ]),
595
+ async (argv) => {
596
+ const context = await createCliContext(argv, abortController!);
597
+ const exitCode = await handleCleanCommand(context, {
598
+ confirm: argv.yes,
599
+ cwd: argv.cwd,
600
+ maxAge: argv['max-age'],
601
+ maxRuns: argv['max-runs'],
602
+ maxSize: argv['max-size'],
603
+ quiet: argv.quiet,
604
+ verbose: argv.verbose,
605
+ });
606
+ process.exit(exitCode);
607
+ },
608
+ )
609
+ .command(
610
+ 'export',
611
+ 'Export benchmark history to a file',
612
+ (yargs) =>
613
+ yargs
614
+ .option('format', {
615
+ choices: ['json', 'csv'] as const,
616
+ defaultDescription: 'json' as const,
617
+ description: 'Export format',
618
+ type: 'string',
619
+ })
620
+ .option('output', {
621
+ alias: 'o',
622
+ demandOption: true,
623
+ description: 'Output file path',
624
+ type: 'string',
625
+ })
626
+ .option('since', {
627
+ description: 'Export runs since date',
628
+ type: 'string',
629
+ })
630
+ .option('until', {
631
+ description: 'Export runs until date',
632
+ type: 'string',
633
+ })
634
+ .example([
635
+ [
636
+ '$0 history export -o history.json',
637
+ 'Export all history to JSON',
638
+ ],
639
+ [
640
+ '$0 history export -o history.csv --format csv',
641
+ 'Export to CSV',
642
+ ],
643
+ [
644
+ '$0 history export -o recent.json --since "1 week ago"',
645
+ 'Export recent runs',
646
+ ],
647
+ ]),
648
+ async (argv) => {
649
+ const context = await createCliContext(argv, abortController!);
650
+ const exitCode = await handleExportCommand(context, {
651
+ cwd: argv.cwd,
652
+ format: argv.format,
653
+ outputPath: argv.output,
654
+ quiet: Boolean(argv.quiet),
655
+ since: argv.since,
656
+ until: argv.until,
657
+ verbose: argv.verbose,
658
+ });
659
+ process.exitCode = exitCode;
660
+ },
661
+ )
662
+ .demandCommand(1, 'You must specify a history subcommand')
663
+ .strict()
664
+ .example([
665
+ ['$0 history list', 'List recent benchmark runs'],
666
+ ['$0 history show <run-id>', 'Show detailed results'],
667
+ ['$0 history compare <run-id1> <run-id2>', 'Compare two runs'],
668
+ ['$0 history trends', 'Show performance trends'],
669
+ ['$0 history clean --max-runs 50', 'Keep only latest 50 runs'],
670
+ ['$0 history export -o data.json', 'Export history'],
671
+ ]),
421
672
  )
673
+ .command('baseline', 'Manage performance baselines', (yargs) => {
674
+ return yargs
675
+ .command(
676
+ 'set <name>',
677
+ 'Save a benchmark run as a baseline',
678
+ (yargs) => {
679
+ return yargs
680
+ .positional('name', {
681
+ describe: 'Name for the baseline',
682
+ type: 'string',
683
+ })
684
+ .option('run-id', {
685
+ description: 'Specific run ID to save (default: most recent)',
686
+ type: 'string',
687
+ })
688
+ .option('commit', {
689
+ description: 'Git commit SHA (40 characters)',
690
+ type: 'string',
691
+ })
692
+ .option('branch', {
693
+ description: 'Git branch name',
694
+ type: 'string',
695
+ })
696
+ .option('default', {
697
+ defaultDescription: 'false',
698
+ description: 'Set as default baseline',
699
+ type: 'boolean',
700
+ })
701
+ .example([
702
+ [
703
+ '$0 baseline set production-v1.0',
704
+ 'Save most recent run as baseline',
705
+ ],
706
+ ['$0 baseline set v1.0 --default', 'Save and set as default'],
707
+ [
708
+ '$0 baseline set v1.0 --commit abc123...',
709
+ 'Save with commit info',
710
+ ],
711
+ ]);
712
+ },
713
+ async (argv) => {
714
+ const context = await createCliContext(argv, abortController!);
715
+ const exitCode = await handleBaselineSetCommand(context, {
716
+ branch: argv.branch,
717
+ commit: argv.commit,
718
+ cwd: argv.cwd,
719
+ default: argv.default,
720
+ name: String(argv.name),
721
+ quiet: Boolean(argv.quiet),
722
+ runId: argv['run-id'],
723
+ verbose: argv.verbose,
724
+ });
725
+ process.exit(exitCode);
726
+ },
727
+ )
728
+ .command(
729
+ 'list',
730
+ 'List all saved baselines',
731
+ (yargs) => {
732
+ return yargs
733
+ .option('format', {
734
+ choices: ['human', 'json'] as const,
735
+ defaultDescription: 'human' as const,
736
+ description: 'Output format',
737
+ type: 'string',
738
+ })
739
+ .example([
740
+ ['$0 baseline list', 'List all baselines'],
741
+ ['$0 baseline list --format json', 'List in JSON format'],
742
+ ]);
743
+ },
744
+ async (argv) => {
745
+ const context = await createCliContext(argv, abortController!);
746
+ const exitCode = await handleBaselineListCommand(context, {
747
+ cwd: argv.cwd,
748
+ format: argv.format,
749
+ quiet: Boolean(argv.quiet),
750
+ verbose: argv.verbose,
751
+ });
752
+ process.exit(exitCode);
753
+ },
754
+ )
755
+ .command(
756
+ 'show <name>',
757
+ 'Show baseline details',
758
+ (yargs) => {
759
+ return yargs
760
+ .positional('name', {
761
+ describe: 'Baseline name to show',
762
+ type: 'string',
763
+ })
764
+ .option('format', {
765
+ choices: ['human', 'json'] as const,
766
+ defaultDescription: 'human' as const,
767
+ description: 'Output format',
768
+ type: 'string',
769
+ })
770
+ .example([
771
+ ['$0 baseline show production-v1.0', 'Show baseline details'],
772
+ [
773
+ '$0 baseline show v1.0 --format json',
774
+ 'Show in JSON format',
775
+ ],
776
+ ]);
777
+ },
778
+ async (argv) => {
779
+ const context = await createCliContext(argv, abortController!);
780
+ const exitCode = await handleBaselineShowCommand(context, {
781
+ cwd: argv.cwd,
782
+ format: argv.format,
783
+ name: String(argv.name),
784
+ quiet: Boolean(argv.quiet),
785
+ verbose: argv.verbose,
786
+ });
787
+ process.exit(exitCode);
788
+ },
789
+ )
790
+ .command(
791
+ 'delete <name>',
792
+ 'Delete a baseline',
793
+ (yargs) => {
794
+ return yargs
795
+ .positional('name', {
796
+ describe: 'Baseline name to delete',
797
+ type: 'string',
798
+ })
799
+ .example([
800
+ ['$0 baseline delete old-baseline', 'Delete a baseline'],
801
+ ]);
802
+ },
803
+ async (argv) => {
804
+ const context = await createCliContext(argv, abortController!);
805
+ const exitCode = await handleBaselineDeleteCommand(context, {
806
+ cwd: argv.cwd,
807
+ name: String(argv.name),
808
+ quiet: Boolean(argv.quiet),
809
+ verbose: argv.verbose,
810
+ });
811
+ process.exit(exitCode);
812
+ },
813
+ )
814
+ .command(
815
+ 'analyze',
816
+ 'Analyze history and suggest performance budgets',
817
+ (yargs) => {
818
+ return yargs
819
+ .option('runs', {
820
+ defaultDescription: '10',
821
+ description: 'Number of recent runs to analyze',
822
+ type: 'number',
823
+ })
824
+ .option('confidence', {
825
+ defaultDescription: '0.95',
826
+ description: 'Confidence level (0.5-0.999, default 0.95)',
827
+ type: 'number',
828
+ })
829
+ .example([
830
+ [
831
+ '$0 baseline analyze',
832
+ 'Analyze last 10 runs with 95% confidence',
833
+ ],
834
+ ['$0 baseline analyze --runs 20', 'Analyze last 20 runs'],
835
+ [
836
+ '$0 baseline analyze --confidence 0.90',
837
+ 'Use 90% confidence level',
838
+ ],
839
+ ]);
840
+ },
841
+ async (argv) => {
842
+ const context = await createCliContext(argv, abortController!);
843
+ const exitCode = await handleBaselineAnalyzeCommand(context, {
844
+ confidence: argv.confidence,
845
+ cwd: argv.cwd,
846
+ quiet: Boolean(argv.quiet),
847
+ runs: argv.runs,
848
+ verbose: argv.verbose,
849
+ });
850
+ process.exit(exitCode);
851
+ },
852
+ )
853
+ .demandCommand(1, 'You must specify a baseline subcommand')
854
+ .strict()
855
+ .example([
856
+ ['$0 baseline set production-v1.0', 'Save current run as baseline'],
857
+ ['$0 baseline list', 'List all baselines'],
858
+ ['$0 baseline show production-v1.0', 'Show baseline details'],
859
+ ['$0 baseline delete old-baseline', 'Delete a baseline'],
860
+ ['$0 baseline analyze', 'Suggest budgets from history'],
861
+ ]);
862
+ })
422
863
  .command(
423
864
  'init [type]',
424
865
  'Initialize a new benchmark project',
@@ -426,35 +867,35 @@ export const main = async (
426
867
  return yargs
427
868
  .positional('type', {
428
869
  choices: ['basic', 'advanced', 'library'] as const,
429
- default: 'basic' as const,
870
+ defaultDescription: 'basic' as const,
430
871
  describe: 'Type of project to initialize',
431
872
  type: 'string',
432
873
  })
433
874
  .option('examples', {
434
- default: true,
875
+ defaultDescription: 'true',
435
876
  description: 'Include example benchmark files',
436
877
  type: 'boolean',
437
878
  })
438
879
  .option('config-type', {
439
880
  choices: ['json', 'yaml', 'js', 'ts'] as const,
440
- default: 'json' as const,
881
+ defaultDescription: 'json' as const,
441
882
  description: 'Configuration file format',
442
883
  type: 'string',
443
884
  })
444
885
  .option('force', {
445
- default: false,
886
+ defaultDescription: 'false',
446
887
  description: 'Overwrite existing files',
447
888
  type: 'boolean',
448
889
  })
449
890
  .option('yes', {
450
891
  alias: 'y',
451
- default: false,
892
+ defaultDescription: 'false',
452
893
  description: 'Accept all prompts automatically',
453
894
  type: 'boolean',
454
895
  })
455
896
  .option('quiet', {
456
897
  alias: 'q',
457
- default: false,
898
+ defaultDescription: 'false',
458
899
  description: 'Minimal output',
459
900
  type: 'boolean',
460
901
  })
@@ -477,24 +918,87 @@ export const main = async (
477
918
  cwd: argv.cwd,
478
919
  examples: argv.examples,
479
920
  force: argv.force,
480
- quiet: Boolean(argv.quiet),
921
+ quiet: argv.quiet,
481
922
  type: argv.type,
482
923
  verbose: argv.verbose,
483
924
  yes: argv.yes,
484
925
  });
485
- process.exit(exitCode);
926
+ process.exitCode = exitCode;
927
+ },
928
+ )
929
+ .command(
930
+ ['analyze [command]', 'profile [command]'],
931
+ 'Analyze code execution and identify benchmark candidates',
932
+ (yargs) => {
933
+ return yargs
934
+ .positional('command', {
935
+ description: 'Command to analyze (e.g., "npm test")',
936
+ type: 'string',
937
+ })
938
+ .option('input', {
939
+ alias: 'i',
940
+ description: 'Path to existing *.cpuprofile file',
941
+ type: 'string',
942
+ })
943
+ .option('filter-file', {
944
+ description: 'Filter functions by file glob pattern',
945
+ type: 'string',
946
+ })
947
+ .option('min-percent', {
948
+ alias: ['m', 'min'],
949
+ default: 0.5,
950
+ description: 'Minimum execution percentage to show',
951
+ type: 'number',
952
+ })
953
+ .option('top', {
954
+ alias: 'n',
955
+ default: 25,
956
+ description: 'Number of top functions to show',
957
+ type: 'number',
958
+ })
959
+ .option('group-by-file', {
960
+ default: false,
961
+ description: 'Group results by file',
962
+ type: 'boolean',
963
+ })
964
+ .check((argv) => {
965
+ if (!argv.command && !argv.input) {
966
+ throw new Error('Either [command] or --input must be provided');
967
+ }
968
+ return true;
969
+ });
970
+ },
971
+ async (argv) => {
972
+ // Context not needed for analyze command currently
973
+ const context = {} as CliContext;
974
+
975
+ const options: AnalyzeOptions = {
976
+ color: !argv.noColor,
977
+ command: argv.command,
978
+ cwd: argv.cwd || process.cwd(),
979
+ filterFile: argv.filterFile,
980
+ groupByFile: argv.groupByFile,
981
+ input: argv.input,
982
+ minPercent: argv.minPercent,
983
+ top: argv.top,
984
+ };
985
+
986
+ process.exitCode = await analyzeCommand(context, options);
486
987
  },
487
988
  )
488
- .fail((msg: string, err: Error, yargsInstance: Argv) => {
989
+ .fail((msg, err, yargs) => {
489
990
  if (err) {
490
991
  console.error('Error:', err.message);
491
992
  if (process.env.DEBUG) {
492
993
  console.error(err.stack);
493
994
  }
494
995
  // Show help for file discovery errors (similar to usage errors)
495
- if (err.name === 'FileDiscoveryError') {
996
+ if (
997
+ isModestBenchError(err) &&
998
+ err.code === ErrorCodes.FILE_DISCOVERY_FAILED
999
+ ) {
496
1000
  console.error();
497
- yargsInstance.showHelp();
1001
+ yargs.showHelp();
498
1002
  process.exit(ExitCodes.DISCOVERY_ERROR);
499
1003
  }
500
1004
  process.exit(ExitCodes.RUNTIME_ERROR);
@@ -502,7 +1006,7 @@ export const main = async (
502
1006
  // Show help for usage errors (unknown arguments, etc.)
503
1007
  console.error(msg);
504
1008
  console.error();
505
- yargsInstance.showHelp();
1009
+ yargs.showHelp();
506
1010
  process.exit(ExitCodes.CONFIG_ERROR);
507
1011
  }
508
1012
  })
@@ -525,20 +1029,20 @@ export const main = async (
525
1029
  const createCliContext = async (
526
1030
  options: GlobalOptions,
527
1031
  abortController: AbortController,
528
- engineType: 'accurate' | 'tinybench' = 'tinybench',
1032
+ engineType: Engine = DEFAULT_ENGINE,
529
1033
  ): Promise<CliContext> => {
530
1034
  try {
531
1035
  const dependencies = bootstrap();
532
1036
 
533
1037
  // Select engine based on type
534
1038
  const engine =
535
- engineType === 'accurate'
1039
+ engineType === Engines.ACCURATE
536
1040
  ? new AccurateEngine(dependencies)
537
1041
  : new TinybenchEngine(dependencies);
538
1042
 
539
1043
  // Register built-in reporters
540
1044
  engine.registerReporter(
541
- 'human',
1045
+ Reporters.HUMAN,
542
1046
  new HumanReporter({
543
1047
  color: !options.noColor,
544
1048
  verbose: options.verbose,
@@ -591,7 +1095,7 @@ const createCliContext = async (
591
1095
  const setupSignalHandlers = (abortController: AbortController): void => {
592
1096
  let abortRequested = false;
593
1097
 
594
- const handleSignal = (signal: string) => {
1098
+ const handleSignal = (signal: NodeJS.Signals) => {
595
1099
  if (abortRequested) {
596
1100
  // Second signal, force exit
597
1101
  console.log(`\nReceived ${signal} again, forcing exit...`);
@@ -606,36 +1110,29 @@ const setupSignalHandlers = (abortController: AbortController): void => {
606
1110
  setTimeout(() => {
607
1111
  console.log('\nBenchmark aborted.');
608
1112
  process.exit(computeExitCode(signal));
609
- }, 100);
1113
+ }, ABORT_TIMEOUT);
610
1114
  };
611
1115
 
612
- process.on('SIGINT', () => handleSignal('SIGINT'));
613
- process.on('SIGQUIT', () => handleSignal('SIGQUIT'));
614
- process.on('SIGTERM', () => handleSignal('SIGTERM'));
615
-
616
- process.once('uncaughtException', (error) => {
617
- // Wrap non-ModestBench errors with UnknownError
618
- const wrappedError: Error = isModestBenchError(error)
619
- ? error
620
- : new UnknownError(
621
- error instanceof Error ? error.message : String(error),
622
- { cause: error },
623
- );
624
- console.error(wrappedError.toString());
625
- process.exit(ExitCodes.RUNTIME_ERROR);
626
- });
627
-
628
- process.once('unhandledRejection', (reason) => {
629
- // Wrap non-ModestBench errors with UnknownError
630
- const wrappedError: Error = isModestBenchError(reason)
631
- ? (reason as Error)
632
- : new UnknownError(
633
- reason instanceof Error ? reason.message : String(reason),
634
- { cause: reason },
635
- );
636
- console.error(wrappedError.toString());
637
- process.exit(ExitCodes.RUNTIME_ERROR);
638
- });
1116
+ process
1117
+ .once('SIGINT', handleSignal)
1118
+ .once('SIGQUIT', handleSignal)
1119
+ .once('SIGTERM', handleSignal)
1120
+ .once('uncaughtException', (error) => {
1121
+ // Wrap non-ModestBench errors with UnknownError
1122
+ const wrappedError: Error = isModestBenchError(error)
1123
+ ? error
1124
+ : new UnknownError(error.message, { cause: error });
1125
+ console.error(`${wrappedError}`);
1126
+ process.exit(ExitCodes.RUNTIME_ERROR);
1127
+ })
1128
+ .once('unhandledRejection', (reason) => {
1129
+ const wrappedError = new UnknownError(
1130
+ isError(reason) ? reason.message : String(reason),
1131
+ { cause: reason },
1132
+ );
1133
+ console.error(`${wrappedError}`);
1134
+ process.exit(ExitCodes.RUNTIME_ERROR);
1135
+ });
639
1136
  };
640
1137
 
641
1138
  // Run CLI if this file is executed directly
@@ -663,6 +1160,6 @@ try {
663
1160
  * @param signal - The signal that caused the exit
664
1161
  * @returns The exit code
665
1162
  */
666
- const computeExitCode = (signal: string): number => {
1163
+ const computeExitCode = (signal: NodeJS.Signals): number => {
667
1164
  return 128 + (signal === 'SIGINT' ? 2 : signal === 'SIGQUIT' ? 3 : 15);
668
1165
  };