modestbench 0.2.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 (332) hide show
  1. package/CHANGELOG.md +20 -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 +88 -155
  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 +88 -155
  30. package/dist/cli/commands/init.js.map +1 -1
  31. package/dist/cli/commands/run.cjs +132 -114
  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 +131 -80
  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 +103 -21
  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 +104 -22
  78. package/dist/core/engine.js.map +1 -1
  79. package/dist/core/output-path-resolver.cjs +8 -1
  80. package/dist/core/output-path-resolver.cjs.map +1 -1
  81. package/dist/core/output-path-resolver.d.cts.map +1 -1
  82. package/dist/core/output-path-resolver.d.ts.map +1 -1
  83. package/dist/core/output-path-resolver.js +9 -2
  84. package/dist/core/output-path-resolver.js.map +1 -1
  85. package/dist/errors/base.cjs +12 -3
  86. package/dist/errors/base.cjs.map +1 -1
  87. package/dist/errors/base.d.cts +7 -0
  88. package/dist/errors/base.d.cts.map +1 -1
  89. package/dist/errors/base.d.ts +7 -0
  90. package/dist/errors/base.d.ts.map +1 -1
  91. package/dist/errors/base.js +10 -2
  92. package/dist/errors/base.js.map +1 -1
  93. package/dist/errors/budget.cjs +37 -0
  94. package/dist/errors/budget.cjs.map +1 -0
  95. package/dist/errors/budget.d.cts +31 -0
  96. package/dist/errors/budget.d.cts.map +1 -0
  97. package/dist/errors/budget.d.ts +31 -0
  98. package/dist/errors/budget.d.ts.map +1 -0
  99. package/dist/errors/budget.js +33 -0
  100. package/dist/errors/budget.js.map +1 -0
  101. package/dist/errors/index.cjs +4 -1
  102. package/dist/errors/index.cjs.map +1 -1
  103. package/dist/errors/index.d.cts +1 -0
  104. package/dist/errors/index.d.cts.map +1 -1
  105. package/dist/errors/index.d.ts +1 -0
  106. package/dist/errors/index.d.ts.map +1 -1
  107. package/dist/errors/index.js +2 -0
  108. package/dist/errors/index.js.map +1 -1
  109. package/dist/index.cjs +13 -1
  110. package/dist/index.cjs.map +1 -1
  111. package/dist/index.d.cts +5 -0
  112. package/dist/index.d.cts.map +1 -1
  113. package/dist/index.d.ts +5 -0
  114. package/dist/index.d.ts.map +1 -1
  115. package/dist/index.js +7 -0
  116. package/dist/index.js.map +1 -1
  117. package/dist/reporters/csv.cjs +37 -17
  118. package/dist/reporters/csv.cjs.map +1 -1
  119. package/dist/reporters/csv.d.cts +3 -6
  120. package/dist/reporters/csv.d.cts.map +1 -1
  121. package/dist/reporters/csv.d.ts +3 -6
  122. package/dist/reporters/csv.d.ts.map +1 -1
  123. package/dist/reporters/csv.js +37 -17
  124. package/dist/reporters/csv.js.map +1 -1
  125. package/dist/reporters/human.cjs +66 -40
  126. package/dist/reporters/human.cjs.map +1 -1
  127. package/dist/reporters/human.d.cts +14 -13
  128. package/dist/reporters/human.d.cts.map +1 -1
  129. package/dist/reporters/human.d.ts +14 -13
  130. package/dist/reporters/human.d.ts.map +1 -1
  131. package/dist/reporters/human.js +66 -40
  132. package/dist/reporters/human.js.map +1 -1
  133. package/dist/reporters/json.cjs +23 -48
  134. package/dist/reporters/json.cjs.map +1 -1
  135. package/dist/reporters/json.d.cts +2 -28
  136. package/dist/reporters/json.d.cts.map +1 -1
  137. package/dist/reporters/json.d.ts +2 -28
  138. package/dist/reporters/json.d.ts.map +1 -1
  139. package/dist/reporters/json.js +25 -50
  140. package/dist/reporters/json.js.map +1 -1
  141. package/dist/reporters/profile-human.cjs +149 -0
  142. package/dist/reporters/profile-human.cjs.map +1 -0
  143. package/dist/reporters/profile-human.d.cts +44 -0
  144. package/dist/reporters/profile-human.d.cts.map +1 -0
  145. package/dist/reporters/profile-human.d.ts +44 -0
  146. package/dist/reporters/profile-human.d.ts.map +1 -0
  147. package/dist/reporters/profile-human.js +142 -0
  148. package/dist/reporters/profile-human.js.map +1 -0
  149. package/dist/reporters/simple.cjs +64 -44
  150. package/dist/reporters/simple.cjs.map +1 -1
  151. package/dist/reporters/simple.d.cts +14 -14
  152. package/dist/reporters/simple.d.cts.map +1 -1
  153. package/dist/reporters/simple.d.ts +14 -14
  154. package/dist/reporters/simple.d.ts.map +1 -1
  155. package/dist/reporters/simple.js +64 -44
  156. package/dist/reporters/simple.js.map +1 -1
  157. package/dist/schema/modestbench-config.schema.json +153 -0
  158. package/dist/services/baseline-storage.cjs +151 -0
  159. package/dist/services/baseline-storage.cjs.map +1 -0
  160. package/dist/services/baseline-storage.d.cts +55 -0
  161. package/dist/services/baseline-storage.d.cts.map +1 -0
  162. package/dist/services/baseline-storage.d.ts +55 -0
  163. package/dist/services/baseline-storage.d.ts.map +1 -0
  164. package/dist/services/baseline-storage.js +147 -0
  165. package/dist/services/baseline-storage.js.map +1 -0
  166. package/dist/services/budget-evaluator.cjs +146 -0
  167. package/dist/services/budget-evaluator.cjs.map +1 -0
  168. package/dist/services/budget-evaluator.d.cts +29 -0
  169. package/dist/services/budget-evaluator.d.cts.map +1 -0
  170. package/dist/services/budget-evaluator.d.ts +29 -0
  171. package/dist/services/budget-evaluator.d.ts.map +1 -0
  172. package/dist/services/budget-evaluator.js +142 -0
  173. package/dist/services/budget-evaluator.js.map +1 -0
  174. package/dist/services/config-manager.cjs +23 -9
  175. package/dist/services/config-manager.cjs.map +1 -1
  176. package/dist/services/config-manager.d.cts +6 -1
  177. package/dist/services/config-manager.d.cts.map +1 -1
  178. package/dist/services/config-manager.d.ts +6 -1
  179. package/dist/services/config-manager.d.ts.map +1 -1
  180. package/dist/services/config-manager.js +23 -9
  181. package/dist/services/config-manager.js.map +1 -1
  182. package/dist/services/file-loader.cjs +3 -6
  183. package/dist/services/file-loader.cjs.map +1 -1
  184. package/dist/services/file-loader.d.cts.map +1 -1
  185. package/dist/services/file-loader.d.ts.map +1 -1
  186. package/dist/services/file-loader.js +3 -6
  187. package/dist/services/file-loader.js.map +1 -1
  188. package/dist/services/profiler/profile-filter.cjs +113 -0
  189. package/dist/services/profiler/profile-filter.cjs.map +1 -0
  190. package/dist/services/profiler/profile-filter.d.cts +20 -0
  191. package/dist/services/profiler/profile-filter.d.cts.map +1 -0
  192. package/dist/services/profiler/profile-filter.d.ts +20 -0
  193. package/dist/services/profiler/profile-filter.d.ts.map +1 -0
  194. package/dist/services/profiler/profile-filter.js +109 -0
  195. package/dist/services/profiler/profile-filter.js.map +1 -0
  196. package/dist/services/profiler/profile-parser.cjs +139 -0
  197. package/dist/services/profiler/profile-parser.cjs.map +1 -0
  198. package/dist/services/profiler/profile-parser.d.cts +18 -0
  199. package/dist/services/profiler/profile-parser.d.cts.map +1 -0
  200. package/dist/services/profiler/profile-parser.d.ts +18 -0
  201. package/dist/services/profiler/profile-parser.d.ts.map +1 -0
  202. package/dist/services/profiler/profile-parser.js +132 -0
  203. package/dist/services/profiler/profile-parser.js.map +1 -0
  204. package/dist/services/profiler/profile-runner.cjs +90 -0
  205. package/dist/services/profiler/profile-runner.cjs.map +1 -0
  206. package/dist/services/profiler/profile-runner.d.cts +29 -0
  207. package/dist/services/profiler/profile-runner.d.cts.map +1 -0
  208. package/dist/services/profiler/profile-runner.d.ts +29 -0
  209. package/dist/services/profiler/profile-runner.d.ts.map +1 -0
  210. package/dist/services/profiler/profile-runner.js +86 -0
  211. package/dist/services/profiler/profile-runner.js.map +1 -0
  212. package/dist/services/reporter-registry.cjs +18 -24
  213. package/dist/services/reporter-registry.cjs.map +1 -1
  214. package/dist/services/reporter-registry.d.cts +18 -40
  215. package/dist/services/reporter-registry.d.cts.map +1 -1
  216. package/dist/services/reporter-registry.d.ts +18 -40
  217. package/dist/services/reporter-registry.d.ts.map +1 -1
  218. package/dist/services/reporter-registry.js +18 -24
  219. package/dist/services/reporter-registry.js.map +1 -1
  220. package/dist/types/budgets.cjs +8 -0
  221. package/dist/types/budgets.cjs.map +1 -0
  222. package/dist/types/budgets.d.cts +149 -0
  223. package/dist/types/budgets.d.cts.map +1 -0
  224. package/dist/types/budgets.d.ts +149 -0
  225. package/dist/types/budgets.d.ts.map +1 -0
  226. package/dist/types/budgets.js +7 -0
  227. package/dist/types/budgets.js.map +1 -0
  228. package/dist/types/cli.cjs +2 -11
  229. package/dist/types/cli.cjs.map +1 -1
  230. package/dist/types/cli.d.cts +3 -227
  231. package/dist/types/cli.d.cts.map +1 -1
  232. package/dist/types/cli.d.ts +3 -227
  233. package/dist/types/cli.d.ts.map +1 -1
  234. package/dist/types/cli.js +2 -11
  235. package/dist/types/cli.js.map +1 -1
  236. package/dist/types/core.cjs +6 -1
  237. package/dist/types/core.cjs.map +1 -1
  238. package/dist/types/core.d.cts +13 -2
  239. package/dist/types/core.d.cts.map +1 -1
  240. package/dist/types/core.d.ts +13 -2
  241. package/dist/types/core.d.ts.map +1 -1
  242. package/dist/types/core.js +2 -1
  243. package/dist/types/core.js.map +1 -1
  244. package/dist/types/index.cjs +5 -0
  245. package/dist/types/index.cjs.map +1 -1
  246. package/dist/types/index.d.cts +2 -0
  247. package/dist/types/index.d.cts.map +1 -1
  248. package/dist/types/index.d.ts +2 -0
  249. package/dist/types/index.d.ts.map +1 -1
  250. package/dist/types/index.js +2 -0
  251. package/dist/types/index.js.map +1 -1
  252. package/dist/types/interfaces.d.cts +15 -8
  253. package/dist/types/interfaces.d.cts.map +1 -1
  254. package/dist/types/interfaces.d.ts +15 -8
  255. package/dist/types/interfaces.d.ts.map +1 -1
  256. package/dist/types/profiler.cjs +11 -0
  257. package/dist/types/profiler.cjs.map +1 -0
  258. package/dist/types/profiler.d.cts +100 -0
  259. package/dist/types/profiler.d.cts.map +1 -0
  260. package/dist/types/profiler.d.ts +100 -0
  261. package/dist/types/profiler.d.ts.map +1 -0
  262. package/dist/types/profiler.js +10 -0
  263. package/dist/types/profiler.js.map +1 -0
  264. package/dist/types/utility.cjs.map +1 -1
  265. package/dist/types/utility.d.cts +0 -8
  266. package/dist/types/utility.d.cts.map +1 -1
  267. package/dist/types/utility.d.ts +0 -8
  268. package/dist/types/utility.d.ts.map +1 -1
  269. package/dist/types/utility.js.map +1 -1
  270. package/dist/utils/identifiers.cjs +32 -0
  271. package/dist/utils/identifiers.cjs.map +1 -0
  272. package/dist/utils/identifiers.d.cts +32 -0
  273. package/dist/utils/identifiers.d.cts.map +1 -0
  274. package/dist/utils/identifiers.d.ts +32 -0
  275. package/dist/utils/identifiers.d.ts.map +1 -0
  276. package/dist/utils/identifiers.js +27 -0
  277. package/dist/utils/identifiers.js.map +1 -0
  278. package/dist/utils/package.cjs +40 -0
  279. package/dist/utils/package.cjs.map +1 -0
  280. package/dist/utils/package.d.cts +15 -0
  281. package/dist/utils/package.d.cts.map +1 -0
  282. package/dist/utils/package.d.ts +15 -0
  283. package/dist/utils/package.d.ts.map +1 -0
  284. package/dist/utils/package.js +33 -0
  285. package/dist/utils/package.js.map +1 -0
  286. package/dist/utils/type-guards.cjs +48 -0
  287. package/dist/utils/type-guards.cjs.map +1 -0
  288. package/dist/utils/type-guards.d.cts +22 -0
  289. package/dist/utils/type-guards.d.cts.map +1 -0
  290. package/dist/utils/type-guards.d.ts +22 -0
  291. package/dist/utils/type-guards.d.ts.map +1 -0
  292. package/dist/utils/type-guards.js +43 -0
  293. package/dist/utils/type-guards.js.map +1 -0
  294. package/package.json +10 -10
  295. package/src/cli/commands/analyze.ts +101 -0
  296. package/src/cli/commands/baseline.ts +577 -0
  297. package/src/cli/commands/history.ts +1 -1
  298. package/src/cli/commands/init.ts +105 -183
  299. package/src/cli/commands/run.ts +167 -98
  300. package/src/cli/index.ts +425 -183
  301. package/src/config/budget-schema.ts +189 -0
  302. package/src/config/schema.ts +260 -1
  303. package/src/constants.ts +53 -1
  304. package/src/core/engine.ts +151 -20
  305. package/src/core/output-path-resolver.ts +10 -2
  306. package/src/errors/base.ts +11 -2
  307. package/src/errors/budget.ts +38 -0
  308. package/src/errors/index.ts +3 -0
  309. package/src/index.ts +9 -0
  310. package/src/reporters/csv.ts +54 -25
  311. package/src/reporters/human.ts +88 -47
  312. package/src/reporters/json.ts +26 -71
  313. package/src/reporters/profile-human.ts +204 -0
  314. package/src/reporters/simple.ts +84 -53
  315. package/src/services/baseline-storage.ts +199 -0
  316. package/src/services/budget-evaluator.ts +182 -0
  317. package/src/services/config-manager.ts +23 -8
  318. package/src/services/file-loader.ts +3 -6
  319. package/src/services/profiler/profile-filter.ts +143 -0
  320. package/src/services/profiler/profile-parser.ts +194 -0
  321. package/src/services/profiler/profile-runner.ts +121 -0
  322. package/src/services/reporter-registry.ts +46 -81
  323. package/src/types/budgets.ts +180 -0
  324. package/src/types/cli.ts +5 -238
  325. package/src/types/core.ts +50 -10
  326. package/src/types/index.ts +5 -0
  327. package/src/types/interfaces.ts +16 -6
  328. package/src/types/profiler.ts +132 -0
  329. package/src/types/utility.ts +0 -10
  330. package/src/utils/identifiers.ts +58 -0
  331. package/src/utils/package.ts +35 -0
  332. 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;
@@ -85,10 +104,9 @@ export const handleRunCommand = async (
85
104
  if (showCliMessages) {
86
105
  console.error('Setting up reporters...');
87
106
  }
88
- const reporters = await setupReporters(
107
+ const reporters = setupReporters(
89
108
  context,
90
109
  config,
91
- shouldBeQuiet,
92
110
  verbose,
93
111
  showCliMessages,
94
112
  options.quiet ?? false,
@@ -144,7 +162,7 @@ export const handleRunCommand = async (
144
162
  console.error(` ${error.code}: ${error.message}`);
145
163
  }
146
164
  }
147
- return ExitCodes.ValidationError;
165
+ return ExitCodes.VALIDATION_ERROR;
148
166
  }
149
167
 
150
168
  // Step 5: Execution phase
@@ -176,39 +194,66 @@ export const handleRunCommand = async (
176
194
 
177
195
  return handleResults(executionResult, options, shouldBeQuiet);
178
196
  } catch (error) {
197
+ // Check if error has a code property before accessing it
198
+ const errorCode = hasErrorCode(error) ? error.code : undefined;
199
+
200
+ // Handle budget exceeded error
201
+ if (errorCode === ErrorCodes.BUDGET_EXCEEDED) {
202
+ if (!shouldBeQuiet) {
203
+ const budgetError = error as BudgetExceededError;
204
+ console.error(`\n❌ ${budgetError.message}`);
205
+ if (
206
+ budgetError.budgetSummary &&
207
+ budgetError.budgetSummary.results.length > 0
208
+ ) {
209
+ console.error('\nFailed budgets:');
210
+ for (const result of budgetError.budgetSummary.results) {
211
+ if (!result.passed) {
212
+ console.error(` • ${result.taskId}`);
213
+ for (const violation of result.violations) {
214
+ console.error(` ${violation.message}`);
215
+ }
216
+ }
217
+ }
218
+ }
219
+ }
220
+ return ExitCodes.BENCHMARK_FAILURES;
221
+ }
222
+
179
223
  // Re-throw CLI errors so yargs fail handler can show help
180
- if ((error as ModestBenchError).code === ErrorCodes.FILE_DISCOVERY_FAILED) {
224
+ if (errorCode === ErrorCodes.FILE_DISCOVERY_FAILED) {
181
225
  throw error;
182
226
  }
183
- if ((error as ModestBenchError).code === ErrorCodes.CLI_INVALID_ARGUMENT) {
227
+ if (errorCode === ErrorCodes.CLI_INVALID_ARGUMENT) {
184
228
  throw error;
185
229
  }
186
230
 
187
231
  if (!shouldBeQuiet) {
188
- console.error(
189
- `Error: ${error instanceof Error ? error.message : String(error)}`,
190
- );
232
+ console.error(`Error: ${isError(error) ? error.message : String(error)}`);
191
233
  }
192
234
 
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;
235
+ // Return appropriate exit code based on error code
236
+ if (
237
+ errorCode === ErrorCodes.CONFIG_LOAD_FAILED ||
238
+ errorCode === ErrorCodes.CONFIG_NOT_FOUND ||
239
+ errorCode === ErrorCodes.CONFIG_VALIDATION_FAILED ||
240
+ errorCode === ErrorCodes.CONFIG_UNSUPPORTED_FORMAT
241
+ ) {
242
+ return ExitCodes.CONFIG_ERROR;
243
+ }
244
+
245
+ if (errorCode === ErrorCodes.FILE_DISCOVERY_FAILED) {
246
+ return ExitCodes.DISCOVERY_ERROR;
247
+ }
248
+
249
+ // Fallback: check error message for cases where proper error types aren't thrown yet
250
+ if (isError(error)) {
251
+ if (error.message.includes('No benchmark files found')) {
252
+ return ExitCodes.DISCOVERY_ERROR;
208
253
  }
209
254
  }
210
255
 
211
- return ExitCodes.GeneralError;
256
+ return ExitCodes.BENCHMARK_FAILURES;
212
257
  }
213
258
  };
214
259
 
@@ -230,11 +275,11 @@ const handleResults = (
230
275
  if (executionResult && executionResult.summary) {
231
276
  // Return error if there are failed tasks OR file-level errors
232
277
  return executionResult.summary.failedTasks > 0 || hasFileErrors
233
- ? ExitCodes.GeneralError
234
- : ExitCodes.Success;
278
+ ? ExitCodes.BENCHMARK_FAILURES
279
+ : ExitCodes.SUCCESS;
235
280
  }
236
281
 
237
- return ExitCodes.Success;
282
+ return ExitCodes.SUCCESS;
238
283
  };
239
284
 
240
285
  /**
@@ -246,17 +291,20 @@ const loadConfiguration = async (context: CliContext, options: RunOptions) => {
246
291
  const cliArgs: Record<string, unknown> = {};
247
292
 
248
293
  // 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)
294
+ // Only pass pattern if explicitly provided (non-empty)
295
+ // Empty array means no CLI pattern, so config file or defaults should be used
296
+ if (options.pattern !== undefined && options.pattern.length > 0) {
252
297
  cliArgs.pattern =
253
298
  options.pattern.length === 1 ? options.pattern[0] : options.pattern;
254
299
  }
255
- if (options.reporters) {
300
+ if (options.reporters && options.reporters.length > 0) {
256
301
  cliArgs.reporters = options.reporters;
257
302
  }
258
303
  if (options.outputDir) {
259
- cliArgs.outputDir = resolve(options.cwd, options.outputDir);
304
+ cliArgs.outputDir = resolve(
305
+ options.cwd ?? process.cwd(),
306
+ options.outputDir,
307
+ );
260
308
  }
261
309
  if (options.iterations) {
262
310
  cliArgs.iterations = options.iterations;
@@ -270,7 +318,7 @@ const loadConfiguration = async (context: CliContext, options: RunOptions) => {
270
318
  if (options.bail !== undefined) {
271
319
  cliArgs.bail = options.bail;
272
320
  }
273
- if (options.exclude) {
321
+ if (options.exclude && options.exclude.length > 0) {
274
322
  cliArgs.exclude = options.exclude;
275
323
  }
276
324
  if (options.timeout) {
@@ -282,24 +330,30 @@ const loadConfiguration = async (context: CliContext, options: RunOptions) => {
282
330
  if (options.verbose !== undefined) {
283
331
  cliArgs.verbose = options.verbose;
284
332
  }
285
- if (options.tags) {
333
+ if (options.tags && options.tags.length > 0) {
286
334
  cliArgs.tags = options.tags;
287
335
  }
288
- if (options.excludeTags) {
336
+ if (options.excludeTags && options.excludeTags.length > 0) {
289
337
  cliArgs.excludeTags = options.excludeTags;
290
338
  }
291
339
 
292
340
  // Load configuration with CLI argument precedence
293
- const config = await context.configManager.load(options.config, cliArgs);
341
+ // Pass command defaults as the base layer
342
+ const config = await context.configManager.load(
343
+ options.config,
344
+ cliArgs,
345
+ RUN_COMMAND_DEFAULTS,
346
+ );
294
347
 
295
348
  return config;
296
349
  } catch (error) {
297
350
  // Re-throw our custom errors
298
- if ((error as ModestBenchError).code === ErrorCodes.CONFIG_LOAD_FAILED) {
351
+ const errorCode = hasErrorCode(error) ? error.code : undefined;
352
+ if (errorCode === ErrorCodes.CONFIG_LOAD_FAILED) {
299
353
  throw error;
300
354
  }
301
355
  throw new InvalidArgumentError(
302
- `Configuration error: ${error instanceof Error ? error.message : String(error)}`,
356
+ `Configuration error: ${isError(error) ? error.message : String(error)}`,
303
357
  { cause: error },
304
358
  );
305
359
  }
@@ -308,10 +362,9 @@ const loadConfiguration = async (context: CliContext, options: RunOptions) => {
308
362
  /**
309
363
  * Setup and configure reporters based on configuration
310
364
  */
311
- const setupReporters = async (
365
+ const setupReporters = (
312
366
  context: CliContext,
313
367
  config: { outputDir?: string; reporters?: string[] },
314
- shouldBeQuiet: boolean,
315
368
  isVerbose: boolean,
316
369
  showCliMessages: boolean,
317
370
  explicitQuiet: boolean,
@@ -321,13 +374,8 @@ const setupReporters = async (
321
374
  ) => {
322
375
  try {
323
376
  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');
377
+ // Dedupe requested reporters
378
+ const requestedReporters = [...new Set(config.reporters || ['human'])];
331
379
 
332
380
  // Only use file output if --output was explicitly provided
333
381
  // Use the explicit output dir if provided, otherwise check config
@@ -335,56 +383,76 @@ const setupReporters = async (
335
383
  ? resolve(explicitOutputDir)
336
384
  : undefined;
337
385
 
386
+ // Built-in reporter names for error messages
387
+ const builtInReporters = ['human', 'json', 'csv', 'simple'];
388
+
338
389
  for (const reporterName of requestedReporters) {
339
390
  let reporter;
340
391
 
341
392
  // 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(', ')}`,
393
+ switch (reporterName) {
394
+ case 'csv': {
395
+ const outputPath = resolveOutputPath(
396
+ outputDir,
397
+ explicitOutputFile,
398
+ 'results.csv',
399
+ );
400
+ reporter = new CsvReporter({
401
+ includeHeaders: true,
402
+ includeMetadata: true,
403
+ ...(outputPath ? { outputPath } : {}),
404
+ quiet: explicitQuiet, // Only applies explicit --quiet flag; CSV output can coexist with progress messages on different streams
405
+ verbose: isVerbose,
406
+ });
407
+ break;
408
+ }
409
+
410
+ case 'human':
411
+ reporter = new HumanReporter({
412
+ color: true,
413
+ progress: progressOption ?? true,
414
+ quiet: explicitQuiet, // Only applies explicit --quiet flag; JSON reporter forcing quiet mode does not affect HumanReporter progress output
415
+ verbose: isVerbose,
416
+ });
417
+ break;
418
+
419
+ case 'json': {
420
+ const outputPath = resolveOutputPath(
421
+ outputDir,
422
+ explicitOutputFile,
423
+ 'results.json',
386
424
  );
425
+ reporter = new JsonReporter({
426
+ ...(outputPath ? { outputPath } : {}),
427
+ prettyPrint: true,
428
+ });
429
+ break;
387
430
  }
431
+
432
+ case 'simple':
433
+ reporter = new SimpleReporter({
434
+ quiet: explicitQuiet,
435
+ verbose: isVerbose,
436
+ });
437
+ break;
438
+
439
+ default:
440
+ // Fall back to registry for custom reporters
441
+ reporter = context.reporterRegistry.get(reporterName);
442
+ if (!reporter) {
443
+ // Combine built-in reporters with registered custom reporters
444
+ const registeredReporters = Object.keys(
445
+ context.reporterRegistry.getAll(),
446
+ );
447
+ const availableReporters = [
448
+ ...builtInReporters,
449
+ ...registeredReporters,
450
+ ];
451
+ throw new UnknownReporterError(
452
+ `Unknown reporter: ${reporterName}. Available: ${availableReporters.join(', ')}`,
453
+ );
454
+ }
455
+ break;
388
456
  }
389
457
 
390
458
  reporters.push(reporter);
@@ -397,11 +465,12 @@ const setupReporters = async (
397
465
  return reporters;
398
466
  } catch (error) {
399
467
  // Re-throw our custom errors
400
- if ((error as ModestBenchError).code === ErrorCodes.REPORTER_UNKNOWN) {
468
+ const errorCode = hasErrorCode(error) ? error.code : undefined;
469
+ if (errorCode === ErrorCodes.REPORTER_UNKNOWN) {
401
470
  throw error;
402
471
  }
403
472
  throw new InvalidArgumentError(
404
- `Reporter setup error: ${error instanceof Error ? error.message : String(error)}`,
473
+ `Reporter setup error: ${isError(error) ? error.message : String(error)}`,
405
474
  { cause: error },
406
475
  );
407
476
  }