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
@@ -7,17 +7,36 @@
7
7
 
8
8
  import { resolve } from 'node:path';
9
9
 
10
- import type { BenchmarkRun } from '../../types/index.js';
10
+ import type { BenchmarkRun, ModestBenchConfig } from '../../types/index.js';
11
11
  import type { CliContext } from '../index.js';
12
12
 
13
13
  import { ErrorCodes } from '../../constants.js';
14
14
  import { resolveOutputPath } from '../../core/output-path-resolver.js';
15
15
  import {
16
+ type BudgetExceededError,
16
17
  InvalidArgumentError,
17
- type ModestBenchError,
18
18
  UnknownReporterError,
19
19
  } from '../../errors/index.js';
20
+ import { CsvReporter } from '../../reporters/csv.js';
21
+ import { HumanReporter } from '../../reporters/human.js';
22
+ import { JsonReporter } from '../../reporters/json.js';
23
+ import { SimpleReporter } from '../../reporters/simple.js';
20
24
  import { ExitCodes } from '../../types/cli.js';
25
+ import { hasErrorCode, isError } from '../../utils/type-guards.js';
26
+
27
+ /**
28
+ * Default values for the run command
29
+ *
30
+ * These are the command-level defaults used when neither config file nor CLI
31
+ * arguments provide values. They represent sensible defaults for running
32
+ * benchmarks.
33
+ */
34
+ export const RUN_COMMAND_DEFAULTS = {
35
+ bail: false,
36
+ quiet: false,
37
+ reporters: ['human'],
38
+ verbose: false,
39
+ } as const satisfies Partial<ModestBenchConfig>;
21
40
 
22
41
  /**
23
42
  * Run command options interface
@@ -25,7 +44,7 @@ import { ExitCodes } from '../../types/cli.js';
25
44
  interface RunOptions {
26
45
  bail?: boolean | undefined;
27
46
  config?: string | undefined;
28
- cwd: string;
47
+ cwd?: string;
29
48
  engine?: 'accurate' | 'tinybench' | undefined;
30
49
  exclude?: string[] | undefined;
31
50
  excludeTags?: string[] | undefined;
@@ -34,10 +53,10 @@ interface RunOptions {
34
53
  noColor?: boolean | undefined;
35
54
  outputDir?: string | undefined;
36
55
  outputFile?: string | undefined;
37
- pattern: string[];
56
+ pattern?: string[] | undefined;
38
57
  progress?: boolean | undefined;
39
58
  quiet?: boolean | undefined;
40
- reporters: string[];
59
+ reporters?: string[] | undefined;
41
60
  tags?: string[] | undefined;
42
61
  time?: number | undefined;
43
62
  timeout?: number | undefined;
@@ -52,15 +71,8 @@ export const handleRunCommand = async (
52
71
  context: CliContext,
53
72
  options: RunOptions,
54
73
  ): Promise<number> => {
55
- // Check if JSON reporter is being used (need quiet output for clean JSON)
56
- // Only force quiet mode if json is used AND no output directory is specified
57
- // (i.e., outputting to stdout where we need clean JSON)
58
- const isUsingJsonReporter = options.reporters?.includes('json') ?? false;
59
- const shouldBeQuiet =
60
- options.quiet || (isUsingJsonReporter && !options.outputDir);
61
74
  const verbose = options.verbose ?? false;
62
- // CLI messages on stderr should only be suppressed by explicit --quiet, not JSON-forced quiet
63
- const showCliMessages = verbose && !options.quiet;
75
+ let shouldBeQuiet = options.quiet ?? false; // Will be updated after config loads
64
76
 
65
77
  try {
66
78
  // Validate --output-file usage
@@ -76,22 +88,27 @@ export const handleRunCommand = async (
76
88
  }
77
89
 
78
90
  // Step 1: Load and merge configuration
79
- if (showCliMessages) {
80
- console.error('Loading configuration...');
81
- }
82
91
  const config = await loadConfiguration(context, options);
83
92
 
93
+ // Check if JSON reporter is being used (need quiet output for clean JSON)
94
+ // Only force quiet mode if json is used AND no output directory is specified
95
+ // (i.e., outputting to stdout where we need clean JSON)
96
+ const isUsingJsonReporter = config.reporters?.includes('json') ?? false;
97
+ const hasOutputDir = !!(options.outputDir || config.outputDir);
98
+ shouldBeQuiet = options.quiet || (isUsingJsonReporter && !hasOutputDir);
99
+ // CLI messages on stderr should only be suppressed by explicit --quiet, not JSON-forced quiet
100
+ const showCliMessages = verbose && !options.quiet;
101
+
84
102
  // Step 2: Configure reporters
85
103
  if (showCliMessages) {
86
104
  console.error('Setting up reporters...');
87
105
  }
88
- const reporters = await setupReporters(
106
+ const reporters = setupReporters(
89
107
  context,
90
108
  config,
91
- shouldBeQuiet,
92
109
  verbose,
93
110
  showCliMessages,
94
- options.quiet ?? false,
111
+ shouldBeQuiet,
95
112
  options.outputDir,
96
113
  options.outputFile,
97
114
  options.progress,
@@ -144,7 +161,7 @@ export const handleRunCommand = async (
144
161
  console.error(` ${error.code}: ${error.message}`);
145
162
  }
146
163
  }
147
- return ExitCodes.ValidationError;
164
+ return ExitCodes.VALIDATION_ERROR;
148
165
  }
149
166
 
150
167
  // Step 5: Execution phase
@@ -176,39 +193,66 @@ export const handleRunCommand = async (
176
193
 
177
194
  return handleResults(executionResult, options, shouldBeQuiet);
178
195
  } catch (error) {
196
+ // Check if error has a code property before accessing it
197
+ const errorCode = hasErrorCode(error) ? error.code : undefined;
198
+
199
+ // Handle budget exceeded error
200
+ if (errorCode === ErrorCodes.BUDGET_EXCEEDED) {
201
+ if (!shouldBeQuiet) {
202
+ const budgetError = error as BudgetExceededError;
203
+ console.error(`\n❌ ${budgetError.message}`);
204
+ if (
205
+ budgetError.budgetSummary &&
206
+ budgetError.budgetSummary.results.length > 0
207
+ ) {
208
+ console.error('\nFailed budgets:');
209
+ for (const result of budgetError.budgetSummary.results) {
210
+ if (!result.passed) {
211
+ console.error(` • ${result.taskId}`);
212
+ for (const violation of result.violations) {
213
+ console.error(` ${violation.message}`);
214
+ }
215
+ }
216
+ }
217
+ }
218
+ }
219
+ return ExitCodes.BENCHMARK_FAILURES;
220
+ }
221
+
179
222
  // Re-throw CLI errors so yargs fail handler can show help
180
- if ((error as ModestBenchError).code === ErrorCodes.FILE_DISCOVERY_FAILED) {
223
+ if (errorCode === ErrorCodes.FILE_DISCOVERY_FAILED) {
181
224
  throw error;
182
225
  }
183
- if ((error as ModestBenchError).code === ErrorCodes.CLI_INVALID_ARGUMENT) {
226
+ if (errorCode === ErrorCodes.CLI_INVALID_ARGUMENT) {
184
227
  throw error;
185
228
  }
186
229
 
187
230
  if (!shouldBeQuiet) {
188
- console.error(
189
- `Error: ${error instanceof Error ? error.message : String(error)}`,
190
- );
231
+ console.error(`Error: ${isError(error) ? error.message : String(error)}`);
191
232
  }
192
233
 
193
- // Return appropriate exit code based on error type
194
- if (error instanceof Error) {
195
- if (
196
- error.message.includes('Configuration error') ||
197
- error.message.includes('Config file not found') ||
198
- error.message.includes('Failed to load config')
199
- ) {
200
- return ExitCodes.ConfigurationError;
201
- }
202
- if (
203
- error.message.includes('No files found') ||
204
- error.message.includes('No benchmark files found') ||
205
- error.message.includes('File discovery')
206
- ) {
207
- return ExitCodes.FileDiscoveryError;
234
+ // Return appropriate exit code based on error code
235
+ if (
236
+ errorCode === ErrorCodes.CONFIG_LOAD_FAILED ||
237
+ errorCode === ErrorCodes.CONFIG_NOT_FOUND ||
238
+ errorCode === ErrorCodes.CONFIG_VALIDATION_FAILED ||
239
+ errorCode === ErrorCodes.CONFIG_UNSUPPORTED_FORMAT
240
+ ) {
241
+ return ExitCodes.CONFIG_ERROR;
242
+ }
243
+
244
+ if (errorCode === ErrorCodes.FILE_DISCOVERY_FAILED) {
245
+ return ExitCodes.DISCOVERY_ERROR;
246
+ }
247
+
248
+ // Fallback: check error message for cases where proper error types aren't thrown yet
249
+ if (isError(error)) {
250
+ if (error.message.includes('No benchmark files found')) {
251
+ return ExitCodes.DISCOVERY_ERROR;
208
252
  }
209
253
  }
210
254
 
211
- return ExitCodes.GeneralError;
255
+ return ExitCodes.BENCHMARK_FAILURES;
212
256
  }
213
257
  };
214
258
 
@@ -230,11 +274,11 @@ const handleResults = (
230
274
  if (executionResult && executionResult.summary) {
231
275
  // Return error if there are failed tasks OR file-level errors
232
276
  return executionResult.summary.failedTasks > 0 || hasFileErrors
233
- ? ExitCodes.GeneralError
234
- : ExitCodes.Success;
277
+ ? ExitCodes.BENCHMARK_FAILURES
278
+ : ExitCodes.SUCCESS;
235
279
  }
236
280
 
237
- return ExitCodes.Success;
281
+ return ExitCodes.SUCCESS;
238
282
  };
239
283
 
240
284
  /**
@@ -246,17 +290,20 @@ const loadConfiguration = async (context: CliContext, options: RunOptions) => {
246
290
  const cliArgs: Record<string, unknown> = {};
247
291
 
248
292
  // Map CLI arguments to config properties
249
- // Pass pattern as-is, even if empty (loader will provide defaults)
250
- if (options.pattern !== undefined) {
251
- // If pattern is provided, use it; if empty array, pass it (for defaults)
293
+ // Only pass pattern if explicitly provided (non-empty)
294
+ // Empty array means no CLI pattern, so config file or defaults should be used
295
+ if (options.pattern !== undefined && options.pattern.length > 0) {
252
296
  cliArgs.pattern =
253
297
  options.pattern.length === 1 ? options.pattern[0] : options.pattern;
254
298
  }
255
- if (options.reporters) {
299
+ if (options.reporters && options.reporters.length > 0) {
256
300
  cliArgs.reporters = options.reporters;
257
301
  }
258
302
  if (options.outputDir) {
259
- cliArgs.outputDir = resolve(options.cwd, options.outputDir);
303
+ cliArgs.outputDir = resolve(
304
+ options.cwd ?? process.cwd(),
305
+ options.outputDir,
306
+ );
260
307
  }
261
308
  if (options.iterations) {
262
309
  cliArgs.iterations = options.iterations;
@@ -270,7 +317,7 @@ const loadConfiguration = async (context: CliContext, options: RunOptions) => {
270
317
  if (options.bail !== undefined) {
271
318
  cliArgs.bail = options.bail;
272
319
  }
273
- if (options.exclude) {
320
+ if (options.exclude && options.exclude.length > 0) {
274
321
  cliArgs.exclude = options.exclude;
275
322
  }
276
323
  if (options.timeout) {
@@ -282,24 +329,30 @@ const loadConfiguration = async (context: CliContext, options: RunOptions) => {
282
329
  if (options.verbose !== undefined) {
283
330
  cliArgs.verbose = options.verbose;
284
331
  }
285
- if (options.tags) {
332
+ if (options.tags && options.tags.length > 0) {
286
333
  cliArgs.tags = options.tags;
287
334
  }
288
- if (options.excludeTags) {
335
+ if (options.excludeTags && options.excludeTags.length > 0) {
289
336
  cliArgs.excludeTags = options.excludeTags;
290
337
  }
291
338
 
292
339
  // Load configuration with CLI argument precedence
293
- const config = await context.configManager.load(options.config, cliArgs);
340
+ // Pass command defaults as the base layer
341
+ const config = await context.configManager.load(
342
+ options.config,
343
+ cliArgs,
344
+ RUN_COMMAND_DEFAULTS,
345
+ );
294
346
 
295
347
  return config;
296
348
  } catch (error) {
297
349
  // Re-throw our custom errors
298
- if ((error as ModestBenchError).code === ErrorCodes.CONFIG_LOAD_FAILED) {
350
+ const errorCode = hasErrorCode(error) ? error.code : undefined;
351
+ if (errorCode === ErrorCodes.CONFIG_LOAD_FAILED) {
299
352
  throw error;
300
353
  }
301
354
  throw new InvalidArgumentError(
302
- `Configuration error: ${error instanceof Error ? error.message : String(error)}`,
355
+ `Configuration error: ${isError(error) ? error.message : String(error)}`,
303
356
  { cause: error },
304
357
  );
305
358
  }
@@ -308,10 +361,9 @@ const loadConfiguration = async (context: CliContext, options: RunOptions) => {
308
361
  /**
309
362
  * Setup and configure reporters based on configuration
310
363
  */
311
- const setupReporters = async (
364
+ const setupReporters = (
312
365
  context: CliContext,
313
366
  config: { outputDir?: string; reporters?: string[] },
314
- shouldBeQuiet: boolean,
315
367
  isVerbose: boolean,
316
368
  showCliMessages: boolean,
317
369
  explicitQuiet: boolean,
@@ -321,70 +373,87 @@ const setupReporters = async (
321
373
  ) => {
322
374
  try {
323
375
  const reporters = [];
324
- const requestedReporters = config.reporters || ['human'];
325
-
326
- // Dynamically import reporters for proper configuration
327
- const { HumanReporter } = await import('../../reporters/human.js');
328
- const { JsonReporter } = await import('../../reporters/json.js');
329
- const { CsvReporter } = await import('../../reporters/csv.js');
330
- const { SimpleReporter } = await import('../../reporters/simple.js');
376
+ // Dedupe requested reporters
377
+ const requestedReporters = [...new Set(config.reporters || ['human'])];
331
378
 
332
- // Only use file output if --output was explicitly provided
333
- // Use the explicit output dir if provided, otherwise check config
379
+ // Use output directory from CLI flag, config file, or undefined
380
+ // CLI flag takes precedence over config file
334
381
  const outputDir = explicitOutputDir
335
382
  ? resolve(explicitOutputDir)
336
- : undefined;
383
+ : config.outputDir
384
+ ? resolve(config.outputDir)
385
+ : undefined;
386
+
387
+ // Built-in reporter names for error messages
388
+ const builtInReporters = ['human', 'json', 'csv', 'simple'];
337
389
 
338
390
  for (const reporterName of requestedReporters) {
339
391
  let reporter;
340
392
 
341
393
  // Create reporter instances with output path configuration
342
- if (reporterName === 'human') {
343
- reporter = new HumanReporter({
344
- color: true,
345
- progress: progressOption ?? true,
346
- quiet: explicitQuiet, // Only applies explicit --quiet flag; JSON reporter forcing quiet mode does not affect HumanReporter progress output
347
- verbose: isVerbose,
348
- });
349
- } else if (reporterName === 'json') {
350
- const outputPath = resolveOutputPath(
351
- outputDir,
352
- explicitOutputFile,
353
- 'results.json',
354
- );
355
- reporter = new JsonReporter({
356
- ...(outputPath ? { outputPath } : {}),
357
- prettyPrint: true,
358
- quiet: shouldBeQuiet, // JSON uses shouldBeQuiet to avoid polluting stdout
359
- verbose: isVerbose,
360
- });
361
- } else if (reporterName === 'csv') {
362
- const outputPath = resolveOutputPath(
363
- outputDir,
364
- explicitOutputFile,
365
- 'results.csv',
366
- );
367
- reporter = new CsvReporter({
368
- includeHeaders: true,
369
- includeMetadata: true,
370
- ...(outputPath ? { outputPath } : {}),
371
- quiet: explicitQuiet, // Only applies explicit --quiet flag; CSV output can coexist with progress messages on different streams
372
- verbose: isVerbose,
373
- });
374
- } else if (reporterName === 'simple') {
375
- reporter = new SimpleReporter({
376
- quiet: explicitQuiet,
377
- verbose: isVerbose,
378
- });
379
- } else {
380
- // Fall back to registry for custom reporters
381
- reporter = context.reporterRegistry.get(reporterName);
382
- if (!reporter) {
383
- const availableReporters = ['human', 'json', 'csv', 'simple'];
384
- throw new UnknownReporterError(
385
- `Unknown reporter: ${reporterName}. Available: ${availableReporters.join(', ')}`,
394
+ switch (reporterName) {
395
+ case 'csv': {
396
+ const outputPath = resolveOutputPath(
397
+ outputDir,
398
+ explicitOutputFile,
399
+ 'results.csv',
386
400
  );
401
+ reporter = new CsvReporter({
402
+ includeHeaders: true,
403
+ includeMetadata: true,
404
+ ...(outputPath ? { outputPath } : {}),
405
+ quiet: explicitQuiet, // Only applies explicit --quiet flag; CSV output can coexist with progress messages on different streams
406
+ verbose: isVerbose,
407
+ });
408
+ break;
387
409
  }
410
+
411
+ case 'human':
412
+ reporter = new HumanReporter({
413
+ color: true,
414
+ progress: progressOption ?? true,
415
+ quiet: explicitQuiet, // Only applies explicit --quiet flag; JSON reporter forcing quiet mode does not affect HumanReporter progress output
416
+ verbose: isVerbose,
417
+ });
418
+ break;
419
+
420
+ case 'json': {
421
+ const outputPath = resolveOutputPath(
422
+ outputDir,
423
+ explicitOutputFile,
424
+ 'results.json',
425
+ );
426
+ reporter = new JsonReporter({
427
+ ...(outputPath ? { outputPath } : {}),
428
+ prettyPrint: true,
429
+ });
430
+ break;
431
+ }
432
+
433
+ case 'simple':
434
+ reporter = new SimpleReporter({
435
+ quiet: explicitQuiet,
436
+ verbose: isVerbose,
437
+ });
438
+ break;
439
+
440
+ default:
441
+ // Fall back to registry for custom reporters
442
+ reporter = context.reporterRegistry.get(reporterName);
443
+ if (!reporter) {
444
+ // Combine built-in reporters with registered custom reporters
445
+ const registeredReporters = Object.keys(
446
+ context.reporterRegistry.getAll(),
447
+ );
448
+ const availableReporters = [
449
+ ...builtInReporters,
450
+ ...registeredReporters,
451
+ ];
452
+ throw new UnknownReporterError(
453
+ `Unknown reporter: ${reporterName}. Available: ${availableReporters.join(', ')}`,
454
+ );
455
+ }
456
+ break;
388
457
  }
389
458
 
390
459
  reporters.push(reporter);
@@ -397,11 +466,12 @@ const setupReporters = async (
397
466
  return reporters;
398
467
  } catch (error) {
399
468
  // Re-throw our custom errors
400
- if ((error as ModestBenchError).code === ErrorCodes.REPORTER_UNKNOWN) {
469
+ const errorCode = hasErrorCode(error) ? error.code : undefined;
470
+ if (errorCode === ErrorCodes.REPORTER_UNKNOWN) {
401
471
  throw error;
402
472
  }
403
473
  throw new InvalidArgumentError(
404
- `Reporter setup error: ${error instanceof Error ? error.message : String(error)}`,
474
+ `Reporter setup error: ${isError(error) ? error.message : String(error)}`,
405
475
  { cause: error },
406
476
  );
407
477
  }