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
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,6 +41,17 @@ import {
32
41
  SimpleReporter,
33
42
  } from '../reporters/index.js';
34
43
  // Import commands
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';
35
55
  import {
36
56
  handleCleanCommand,
37
57
  handleCompareCommand,
@@ -41,7 +61,10 @@ import {
41
61
  handleTrendsCommand,
42
62
  } from './commands/history.js';
43
63
  import { handleInitCommand as initCommand } from './commands/init.js';
44
- import { handleRunCommand as runCommand } from './commands/run.js';
64
+ import {
65
+ RUN_COMMAND_DEFAULTS,
66
+ handleRunCommand as runCommand,
67
+ } from './commands/run.js';
45
68
 
46
69
  /**
47
70
  * CLI context with initialized services
@@ -63,28 +86,15 @@ interface GlobalOptions {
63
86
  /** Configuration file path */
64
87
  config?: string | undefined;
65
88
  /** Working directory */
66
- cwd: string;
89
+ cwd?: string;
67
90
  /** JSON output for machine parsing */
68
- json: boolean;
91
+ json?: boolean;
69
92
  /** Disable colored output */
70
- noColor: boolean;
93
+ noColor?: boolean;
71
94
  /** Enable verbose output */
72
- verbose: boolean;
95
+ verbose?: boolean;
73
96
  }
74
97
 
75
- /**
76
- * Exit codes for the CLI
77
- */
78
- export const ExitCodes = {
79
- BENCHMARK_FAILURES: 1,
80
- CONFIG_ERROR: 2,
81
- DISCOVERY_ERROR: 3,
82
- RUNTIME_ERROR: 5,
83
- SUCCESS: 0,
84
- UNKNOWN_ERROR: 99,
85
- VALIDATION_ERROR: 4,
86
- } as const;
87
-
88
98
  /**
89
99
  * Initialize and run the CLI
90
100
  */
@@ -110,6 +120,7 @@ export const main = async (
110
120
  const cli = yargs(args);
111
121
 
112
122
  // Configure global options and commands
123
+
113
124
  await cli
114
125
  .scriptName('modestbench')
115
126
  .option('config', {
@@ -120,34 +131,34 @@ export const main = async (
120
131
  })
121
132
  .option('verbose', {
122
133
  alias: 'v',
123
- default: false,
134
+ defaultDescription: String(RUN_COMMAND_DEFAULTS.verbose),
124
135
  description: 'Enable verbose output',
125
136
  global: true,
126
137
  type: 'boolean',
127
138
  })
128
139
  .option('no-color', {
129
- default: false,
140
+ defaultDescription: 'false',
130
141
  description: 'Disable colored output',
131
142
  global: true,
132
143
  type: 'boolean',
133
144
  })
134
145
  .option('progress', {
135
- default: true,
146
+ defaultDescription: 'true',
136
147
  description: 'Show animated progress bar',
137
148
  global: true,
138
149
  type: 'boolean',
139
150
  })
140
151
  .option('json', {
141
- default: false,
152
+ defaultDescription: 'false',
142
153
  description: 'Output results in JSON format',
143
154
  global: true,
144
155
  type: 'boolean',
145
156
  })
146
157
  .option('cwd', {
147
- default: process.cwd(),
148
158
  defaultDescription: '.',
149
159
  description: 'Working directory',
150
160
  global: true,
161
+ normalize: true,
151
162
  type: 'string',
152
163
  })
153
164
  .help()
@@ -155,18 +166,18 @@ export const main = async (
155
166
  .version()
156
167
  .alias('version', 'V')
157
168
  .strict()
158
- .demandCommand(1, 'You must specify a command')
169
+ .demandCommand(1)
159
170
  .recommendCommands()
160
171
  .completion()
161
172
  .wrap(Math.min(120, cli.terminalWidth()))
162
173
  .command(
163
174
  ['$0 [pattern..]', 'run [pattern..]'],
164
175
  'Run benchmark files',
165
- (yargs) => {
166
- return yargs
176
+ (yargs) =>
177
+ yargs
167
178
  .positional('pattern', {
168
179
  array: true,
169
- default: ['./bench/**/*.bench.{js,mjs,cjs,ts}'],
180
+ defaultDescription: '(auto-discovered from bench/ directory)',
170
181
  describe:
171
182
  'File paths, directory paths, or glob patterns for benchmark files',
172
183
  type: 'string',
@@ -176,20 +187,13 @@ export const main = async (
176
187
  description: 'Path to configuration file',
177
188
  type: 'string',
178
189
  })
179
- .option('reporters', {
190
+ .option('reporter', {
180
191
  alias: 'r',
181
- coerce: (value: string | string[]) => {
182
- // Handle comma-separated values
183
- if (Array.isArray(value)) {
184
- return value.flatMap((v) =>
185
- v.split(',').map((s) => s.trim()),
186
- );
187
- }
188
- return value.split(',').map((s) => s.trim());
189
- },
190
- default: ['human'],
192
+ array: true,
193
+ choices: Object.values(Reporters).sort(),
194
+ defaultDescription: DEFAULT_REPORTER,
191
195
  description: 'Output reporters to use (human,json,csv)',
192
- type: 'array',
196
+ type: 'string',
193
197
  })
194
198
  .option('output', {
195
199
  alias: 'o',
@@ -197,7 +201,7 @@ export const main = async (
197
201
  type: 'string',
198
202
  })
199
203
  .option('output-file', {
200
- alias: 'of',
204
+ alias: ['of', 'file'],
201
205
  description:
202
206
  'Custom filename for reporter output (use with single reporter only)',
203
207
  requiresArg: true,
@@ -214,11 +218,12 @@ export const main = async (
214
218
  type: 'number',
215
219
  })
216
220
  .option('warmup', {
217
- alias: 'w',
221
+ alias: ['w', 'warm'],
218
222
  description: 'Number of warmup iterations',
219
223
  type: 'number',
220
224
  })
221
225
  .option('limit-by', {
226
+ alias: ['l', 'limit'],
222
227
  choices: ['time', 'iterations', 'any', 'all'],
223
228
  description:
224
229
  'How to limit benchmarks: time (time budget), iterations (sample count), any (either threshold), all (both thresholds)',
@@ -226,22 +231,15 @@ export const main = async (
226
231
  })
227
232
  .option('bail', {
228
233
  alias: 'b',
229
- default: false,
234
+ defaultDescription: String(RUN_COMMAND_DEFAULTS.bail),
230
235
  description: 'Stop on first failure',
231
236
  type: 'boolean',
232
237
  })
233
238
  .option('exclude', {
234
- coerce: (value: string | string[]) => {
235
- // Handle comma-separated values
236
- if (Array.isArray(value)) {
237
- return value.flatMap((v) =>
238
- v.split(',').map((s) => s.trim()),
239
- );
240
- }
241
- return value.split(',').map((s) => s.trim());
242
- },
239
+ alias: 'X',
240
+ array: true,
243
241
  description: 'Exclude patterns (comma-separated)',
244
- type: 'array',
242
+ type: 'string',
245
243
  })
246
244
  .option('timeout', {
247
245
  description: 'Timeout per benchmark in milliseconds',
@@ -249,40 +247,25 @@ export const main = async (
249
247
  })
250
248
  .option('quiet', {
251
249
  alias: 'q',
252
- default: false,
250
+ defaultDescription: String(RUN_COMMAND_DEFAULTS.quiet),
253
251
  description: 'Minimal output',
254
252
  type: 'boolean',
255
253
  })
256
- .option('tags', {
257
- coerce: (value: string | string[]) => {
258
- // Handle comma-separated values
259
- if (Array.isArray(value)) {
260
- return value.flatMap((v) =>
261
- v.split(',').map((s) => s.trim()),
262
- );
263
- }
264
- return value.split(',').map((s) => s.trim());
265
- },
254
+ .option('tag', {
255
+ array: true,
266
256
  description: 'Include only benchmarks with any of these tags',
267
- type: 'array',
257
+ type: 'string',
268
258
  })
269
- .option('exclude-tags', {
270
- coerce: (value: string | string[]) => {
271
- // Handle comma-separated values
272
- if (Array.isArray(value)) {
273
- return value.flatMap((v) =>
274
- v.split(',').map((s) => s.trim()),
275
- );
276
- }
277
- return value.split(',').map((s) => s.trim());
278
- },
259
+ .option('exclude-tag', {
260
+ alias: 'T',
261
+ array: true,
279
262
  description: 'Exclude benchmarks with any of these tags',
280
- type: 'array',
263
+ type: 'string',
281
264
  })
282
265
  .option('engine', {
283
266
  alias: 'e',
284
- choices: ['tinybench', 'accurate'] as const,
285
- default: 'tinybench' as const,
267
+ choices: Object.values(Engines),
268
+ defaultDescription: DEFAULT_ENGINE,
286
269
  description:
287
270
  'Benchmark engine: tinybench (default) or accurate (requires --allow-natives-syntax)',
288
271
  type: 'string',
@@ -294,12 +277,23 @@ export const main = async (
294
277
  ['$0 run "src/**/*.bench.js"', 'Run specific glob pattern'],
295
278
  ['$0 run file1.bench.js file2.bench.js', 'Run specific files'],
296
279
  ['$0 run benchmarks/ tests/perf/', 'Run multiple directories'],
297
- ['$0 run --reporters json,csv', 'Use multiple reporters'],
280
+ ['$0 run -r json -r csv', 'Use multiple reporters'],
298
281
  ['$0 run --iterations 1000', 'Set iteration count'],
299
282
  ['$0 run --engine accurate', 'Use high-accuracy engine'],
300
283
  ['$0 run --bail', 'Stop on first failure'],
301
- ]);
302
- },
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
+ }),
303
297
  async (argv) => {
304
298
  const context = await createCliContext(
305
299
  argv,
@@ -312,7 +306,7 @@ export const main = async (
312
306
  cwd: argv.cwd,
313
307
  engine: argv.engine,
314
308
  exclude: argv.exclude,
315
- excludeTags: argv['exclude-tags'],
309
+ excludeTags: argv['exclude-tag'],
316
310
  iterations: argv.iterations,
317
311
  json: argv.json,
318
312
  noColor: argv.noColor,
@@ -321,8 +315,8 @@ export const main = async (
321
315
  pattern: argv.pattern,
322
316
  progress: argv.progress,
323
317
  quiet: argv.quiet,
324
- reporters: argv.reporters,
325
- tags: argv.tags,
318
+ reporters: argv.reporter,
319
+ tags: argv.tag,
326
320
  time: argv.time,
327
321
  timeout: argv.timeout,
328
322
  verbose: argv.verbose,
@@ -331,13 +325,13 @@ export const main = async (
331
325
  process.exit(exitCode);
332
326
  },
333
327
  )
334
- .command('history', 'View and manage benchmark history', (yargs) => {
335
- return yargs
328
+ .command('history', 'View and manage benchmark history', (yargs) =>
329
+ yargs
336
330
  .command(
337
331
  'list',
338
332
  'List recent benchmark runs',
339
- (yargs) => {
340
- return yargs
333
+ (yargs) =>
334
+ yargs
341
335
  .option('since', {
342
336
  description:
343
337
  'Show runs since date (ISO 8601 or relative like "1 week ago")',
@@ -352,18 +346,20 @@ export const main = async (
352
346
  description: 'Filter by benchmark name pattern',
353
347
  type: 'string',
354
348
  })
355
- .option('tags', {
349
+ .option('tag', {
350
+ alias: 't',
351
+ array: true,
356
352
  description: 'Filter by tags (comma-separated)',
357
- type: 'array',
353
+ type: 'string',
358
354
  })
359
355
  .option('limit', {
360
- default: 10,
356
+ defaultDescription: '10',
361
357
  description: 'Maximum number of results',
362
358
  type: 'number',
363
359
  })
364
360
  .option('format', {
365
361
  choices: ['human', 'json', 'csv'] as const,
366
- default: 'human' as const,
362
+ defaultDescription: 'human' as const,
367
363
  description: 'Output format',
368
364
  type: 'string',
369
365
  })
@@ -375,8 +371,7 @@ export const main = async (
375
371
  ],
376
372
  ['$0 history list --limit 20', 'List 20 most recent runs'],
377
373
  ['$0 history list --format json', 'List runs in JSON format'],
378
- ]);
379
- },
374
+ ]),
380
375
  async (argv) => {
381
376
  const context = await createCliContext(argv, abortController!);
382
377
  const exitCode = await handleListCommand(context, {
@@ -384,9 +379,8 @@ export const main = async (
384
379
  format: argv.format,
385
380
  limit: argv.limit,
386
381
  pattern: argv.pattern,
387
- quiet: Boolean(argv.quiet),
388
382
  since: argv.since,
389
- tags: argv.tags as string[] | undefined,
383
+ tags: argv.tag,
390
384
  until: argv.until,
391
385
  verbose: argv.verbose,
392
386
  });
@@ -396,15 +390,16 @@ export const main = async (
396
390
  .command(
397
391
  'show <run-id>',
398
392
  'Show detailed results for a specific run',
399
- (yargs) => {
400
- return yargs
393
+ (yargs) =>
394
+ yargs
401
395
  .positional('run-id', {
396
+ demandOption: true,
402
397
  describe: 'ID of the benchmark run to show',
403
398
  type: 'string',
404
399
  })
405
400
  .option('format', {
406
401
  choices: ['human', 'json', 'csv'] as const,
407
- default: 'human' as const,
402
+ defaultDescription: 'human' as const,
408
403
  description: 'Output format',
409
404
  type: 'string',
410
405
  })
@@ -417,15 +412,13 @@ export const main = async (
417
412
  '$0 history show abc123 --format json',
418
413
  'Show run in JSON format',
419
414
  ],
420
- ]);
421
- },
415
+ ]),
422
416
  async (argv) => {
423
417
  const context = await createCliContext(argv, abortController!);
424
418
  const exitCode = await handleShowCommand(context, {
425
419
  cwd: argv.cwd,
426
420
  format: argv.format,
427
- quiet: Boolean(argv.quiet),
428
- runId: String(argv['run-id']),
421
+ runId: argv['run-id'],
429
422
  verbose: argv.verbose,
430
423
  });
431
424
  process.exit(exitCode);
@@ -434,19 +427,21 @@ export const main = async (
434
427
  .command(
435
428
  'compare <run-id1> <run-id2>',
436
429
  'Compare two benchmark runs',
437
- (yargs) => {
438
- return yargs
430
+ (yargs) =>
431
+ yargs
439
432
  .positional('run-id1', {
433
+ demandOption: true,
440
434
  describe: 'ID of the first benchmark run',
441
435
  type: 'string',
442
436
  })
443
437
  .positional('run-id2', {
438
+ demandOption: true,
444
439
  describe: 'ID of the second benchmark run',
445
440
  type: 'string',
446
441
  })
447
442
  .option('format', {
448
443
  choices: ['human', 'json'] as const,
449
- default: 'human' as const,
444
+ defaultDescription: 'human' as const,
450
445
  description: 'Output format',
451
446
  type: 'string',
452
447
  })
@@ -456,16 +451,14 @@ export const main = async (
456
451
  '$0 history compare abc123 def456 --format json',
457
452
  'Compare in JSON format',
458
453
  ],
459
- ]);
460
- },
454
+ ]),
461
455
  async (argv) => {
462
456
  const context = await createCliContext(argv, abortController!);
463
457
  const exitCode = await handleCompareCommand(context, {
464
458
  cwd: argv.cwd,
465
459
  format: argv.format,
466
- quiet: Boolean(argv.quiet),
467
- runId1: String(argv['run-id1']),
468
- runId2: String(argv['run-id2']),
460
+ runId1: argv['run-id1'],
461
+ runId2: argv['run-id2'],
469
462
  verbose: argv.verbose,
470
463
  });
471
464
  process.exit(exitCode);
@@ -474,8 +467,8 @@ export const main = async (
474
467
  .command(
475
468
  'trends [pattern]',
476
469
  'Show performance trends over time',
477
- (yargs) => {
478
- return yargs
470
+ (yargs) =>
471
+ yargs
479
472
  .positional('pattern', {
480
473
  describe: 'Filter by benchmark name pattern',
481
474
  type: 'string',
@@ -490,9 +483,11 @@ export const main = async (
490
483
  'Show trends until date (ISO 8601 or relative like "1 day ago")',
491
484
  type: 'string',
492
485
  })
493
- .option('tags', {
486
+ .option('tag', {
487
+ alias: 't',
488
+ array: true,
494
489
  description: 'Filter by tags (comma-separated)',
495
- type: 'array',
490
+ type: 'string',
496
491
  })
497
492
  .option('limit', {
498
493
  description: 'Maximum number of runs to analyze',
@@ -500,13 +495,13 @@ export const main = async (
500
495
  })
501
496
  .option('all', {
502
497
  alias: 'a',
503
- default: false,
498
+ defaultDescription: 'false',
504
499
  description: 'Analyze all runs (ignore limit)',
505
500
  type: 'boolean',
506
501
  })
507
502
  .option('format', {
508
503
  choices: ['human', 'json'] as const,
509
- default: 'human' as const,
504
+ defaultDescription: 'human' as const,
510
505
  description: 'Output format',
511
506
  type: 'string',
512
507
  })
@@ -527,19 +522,17 @@ export const main = async (
527
522
  '$0 history trends --format json',
528
523
  'Output trends in JSON format',
529
524
  ],
530
- ]);
531
- },
525
+ ]),
532
526
  async (argv) => {
533
527
  const context = await createCliContext(argv, abortController!);
534
528
  const exitCode = await handleTrendsCommand(context, {
535
- all: Boolean(argv.all),
529
+ all: argv.all,
536
530
  cwd: argv.cwd,
537
531
  format: argv.format,
538
532
  limit: argv.limit,
539
533
  pattern: argv.pattern,
540
- quiet: Boolean(argv.quiet),
541
534
  since: argv.since,
542
- tags: argv.tags as string[] | undefined,
535
+ tags: argv.tag,
543
536
  until: argv.until,
544
537
  verbose: argv.verbose,
545
538
  });
@@ -549,8 +542,8 @@ export const main = async (
549
542
  .command(
550
543
  'clean',
551
544
  'Clean up old benchmark history',
552
- (yargs) => {
553
- return yargs
545
+ (yargs) =>
546
+ yargs
554
547
  .option('max-age', {
555
548
  description: 'Remove runs older than this many days',
556
549
  type: 'number',
@@ -563,11 +556,16 @@ export const main = async (
563
556
  description: 'Keep history under this size in bytes',
564
557
  type: 'number',
565
558
  })
566
- .option('confirm', {
567
- default: false,
559
+ .option('yes', {
560
+ alias: 'y',
568
561
  description: 'Confirm cleanup without prompting',
569
562
  type: 'boolean',
570
563
  })
564
+ .option('quiet', {
565
+ default: false,
566
+ description: 'Minimal output',
567
+ type: 'boolean',
568
+ })
571
569
  .check((argv) => {
572
570
  if (
573
571
  !argv['max-age'] &&
@@ -582,7 +580,7 @@ export const main = async (
582
580
  })
583
581
  .example([
584
582
  [
585
- '$0 history clean --max-runs 50 --confirm',
583
+ '$0 history clean --max-runs 50 --yes',
586
584
  'Keep only latest 50 runs',
587
585
  ],
588
586
  [
@@ -593,17 +591,16 @@ export const main = async (
593
591
  '$0 history clean --max-size 10485760',
594
592
  'Keep history under 10MB',
595
593
  ],
596
- ]);
597
- },
594
+ ]),
598
595
  async (argv) => {
599
596
  const context = await createCliContext(argv, abortController!);
600
597
  const exitCode = await handleCleanCommand(context, {
601
- confirm: argv.confirm,
598
+ confirm: argv.yes,
602
599
  cwd: argv.cwd,
603
600
  maxAge: argv['max-age'],
604
601
  maxRuns: argv['max-runs'],
605
602
  maxSize: argv['max-size'],
606
- quiet: Boolean(argv.quiet),
603
+ quiet: argv.quiet,
607
604
  verbose: argv.verbose,
608
605
  });
609
606
  process.exit(exitCode);
@@ -612,11 +609,11 @@ export const main = async (
612
609
  .command(
613
610
  'export',
614
611
  'Export benchmark history to a file',
615
- (yargs) => {
616
- return yargs
612
+ (yargs) =>
613
+ yargs
617
614
  .option('format', {
618
615
  choices: ['json', 'csv'] as const,
619
- default: 'json' as const,
616
+ defaultDescription: 'json' as const,
620
617
  description: 'Export format',
621
618
  type: 'string',
622
619
  })
@@ -647,8 +644,7 @@ export const main = async (
647
644
  '$0 history export -o recent.json --since "1 week ago"',
648
645
  'Export recent runs',
649
646
  ],
650
- ]);
651
- },
647
+ ]),
652
648
  async (argv) => {
653
649
  const context = await createCliContext(argv, abortController!);
654
650
  const exitCode = await handleExportCommand(context, {
@@ -660,7 +656,7 @@ export const main = async (
660
656
  until: argv.until,
661
657
  verbose: argv.verbose,
662
658
  });
663
- process.exit(exitCode);
659
+ process.exitCode = exitCode;
664
660
  },
665
661
  )
666
662
  .demandCommand(1, 'You must specify a history subcommand')
@@ -672,6 +668,196 @@ export const main = async (
672
668
  ['$0 history trends', 'Show performance trends'],
673
669
  ['$0 history clean --max-runs 50', 'Keep only latest 50 runs'],
674
670
  ['$0 history export -o data.json', 'Export history'],
671
+ ]),
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'],
675
861
  ]);
676
862
  })
677
863
  .command(
@@ -681,35 +867,35 @@ export const main = async (
681
867
  return yargs
682
868
  .positional('type', {
683
869
  choices: ['basic', 'advanced', 'library'] as const,
684
- default: 'basic' as const,
870
+ defaultDescription: 'basic' as const,
685
871
  describe: 'Type of project to initialize',
686
872
  type: 'string',
687
873
  })
688
874
  .option('examples', {
689
- default: true,
875
+ defaultDescription: 'true',
690
876
  description: 'Include example benchmark files',
691
877
  type: 'boolean',
692
878
  })
693
879
  .option('config-type', {
694
880
  choices: ['json', 'yaml', 'js', 'ts'] as const,
695
- default: 'json' as const,
881
+ defaultDescription: 'json' as const,
696
882
  description: 'Configuration file format',
697
883
  type: 'string',
698
884
  })
699
885
  .option('force', {
700
- default: false,
886
+ defaultDescription: 'false',
701
887
  description: 'Overwrite existing files',
702
888
  type: 'boolean',
703
889
  })
704
890
  .option('yes', {
705
891
  alias: 'y',
706
- default: false,
892
+ defaultDescription: 'false',
707
893
  description: 'Accept all prompts automatically',
708
894
  type: 'boolean',
709
895
  })
710
896
  .option('quiet', {
711
897
  alias: 'q',
712
- default: false,
898
+ defaultDescription: 'false',
713
899
  description: 'Minimal output',
714
900
  type: 'boolean',
715
901
  })
@@ -732,24 +918,87 @@ export const main = async (
732
918
  cwd: argv.cwd,
733
919
  examples: argv.examples,
734
920
  force: argv.force,
735
- quiet: Boolean(argv.quiet),
921
+ quiet: argv.quiet,
736
922
  type: argv.type,
737
923
  verbose: argv.verbose,
738
924
  yes: argv.yes,
739
925
  });
740
- process.exit(exitCode);
926
+ process.exitCode = exitCode;
741
927
  },
742
928
  )
743
- .fail((msg: string, err: Error, yargsInstance: Argv) => {
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);
987
+ },
988
+ )
989
+ .fail((msg, err, yargs) => {
744
990
  if (err) {
745
991
  console.error('Error:', err.message);
746
992
  if (process.env.DEBUG) {
747
993
  console.error(err.stack);
748
994
  }
749
995
  // Show help for file discovery errors (similar to usage errors)
750
- if (err.name === 'FileDiscoveryError') {
996
+ if (
997
+ isModestBenchError(err) &&
998
+ err.code === ErrorCodes.FILE_DISCOVERY_FAILED
999
+ ) {
751
1000
  console.error();
752
- yargsInstance.showHelp();
1001
+ yargs.showHelp();
753
1002
  process.exit(ExitCodes.DISCOVERY_ERROR);
754
1003
  }
755
1004
  process.exit(ExitCodes.RUNTIME_ERROR);
@@ -757,7 +1006,7 @@ export const main = async (
757
1006
  // Show help for usage errors (unknown arguments, etc.)
758
1007
  console.error(msg);
759
1008
  console.error();
760
- yargsInstance.showHelp();
1009
+ yargs.showHelp();
761
1010
  process.exit(ExitCodes.CONFIG_ERROR);
762
1011
  }
763
1012
  })
@@ -780,20 +1029,20 @@ export const main = async (
780
1029
  const createCliContext = async (
781
1030
  options: GlobalOptions,
782
1031
  abortController: AbortController,
783
- engineType: 'accurate' | 'tinybench' = 'tinybench',
1032
+ engineType: Engine = DEFAULT_ENGINE,
784
1033
  ): Promise<CliContext> => {
785
1034
  try {
786
1035
  const dependencies = bootstrap();
787
1036
 
788
1037
  // Select engine based on type
789
1038
  const engine =
790
- engineType === 'accurate'
1039
+ engineType === Engines.ACCURATE
791
1040
  ? new AccurateEngine(dependencies)
792
1041
  : new TinybenchEngine(dependencies);
793
1042
 
794
1043
  // Register built-in reporters
795
1044
  engine.registerReporter(
796
- 'human',
1045
+ Reporters.HUMAN,
797
1046
  new HumanReporter({
798
1047
  color: !options.noColor,
799
1048
  verbose: options.verbose,
@@ -846,7 +1095,7 @@ const createCliContext = async (
846
1095
  const setupSignalHandlers = (abortController: AbortController): void => {
847
1096
  let abortRequested = false;
848
1097
 
849
- const handleSignal = (signal: string) => {
1098
+ const handleSignal = (signal: NodeJS.Signals) => {
850
1099
  if (abortRequested) {
851
1100
  // Second signal, force exit
852
1101
  console.log(`\nReceived ${signal} again, forcing exit...`);
@@ -861,36 +1110,29 @@ const setupSignalHandlers = (abortController: AbortController): void => {
861
1110
  setTimeout(() => {
862
1111
  console.log('\nBenchmark aborted.');
863
1112
  process.exit(computeExitCode(signal));
864
- }, 100);
1113
+ }, ABORT_TIMEOUT);
865
1114
  };
866
1115
 
867
- process.on('SIGINT', () => handleSignal('SIGINT'));
868
- process.on('SIGQUIT', () => handleSignal('SIGQUIT'));
869
- process.on('SIGTERM', () => handleSignal('SIGTERM'));
870
-
871
- process.once('uncaughtException', (error) => {
872
- // Wrap non-ModestBench errors with UnknownError
873
- const wrappedError: Error = isModestBenchError(error)
874
- ? error
875
- : new UnknownError(
876
- error instanceof Error ? error.message : String(error),
877
- { cause: error },
878
- );
879
- console.error(wrappedError.toString());
880
- process.exit(ExitCodes.RUNTIME_ERROR);
881
- });
882
-
883
- process.once('unhandledRejection', (reason) => {
884
- // Wrap non-ModestBench errors with UnknownError
885
- const wrappedError: Error = isModestBenchError(reason)
886
- ? (reason as Error)
887
- : new UnknownError(
888
- reason instanceof Error ? reason.message : String(reason),
889
- { cause: reason },
890
- );
891
- console.error(wrappedError.toString());
892
- process.exit(ExitCodes.RUNTIME_ERROR);
893
- });
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
+ });
894
1136
  };
895
1137
 
896
1138
  // Run CLI if this file is executed directly
@@ -918,6 +1160,6 @@ try {
918
1160
  * @param signal - The signal that caused the exit
919
1161
  * @returns The exit code
920
1162
  */
921
- const computeExitCode = (signal: string): number => {
1163
+ const computeExitCode = (signal: NodeJS.Signals): number => {
922
1164
  return 128 + (signal === 'SIGINT' ? 2 : signal === 'SIGQUIT' ? 3 : 15);
923
1165
  };