modestbench 0.9.1 → 0.9.2

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 (151) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/cli/builder.cjs +259 -0
  3. package/dist/cli/builder.cjs.map +1 -0
  4. package/dist/cli/builder.d.cts +37 -0
  5. package/dist/cli/builder.d.cts.map +1 -0
  6. package/dist/cli/builder.d.ts +37 -0
  7. package/dist/cli/builder.d.ts.map +1 -0
  8. package/dist/cli/builder.js +255 -0
  9. package/dist/cli/builder.js.map +1 -0
  10. package/dist/cli/commands/baseline.cjs +4 -33
  11. package/dist/cli/commands/baseline.cjs.map +1 -1
  12. package/dist/cli/commands/baseline.d.cts.map +1 -1
  13. package/dist/cli/commands/baseline.d.ts.map +1 -1
  14. package/dist/cli/commands/baseline.js +2 -31
  15. package/dist/cli/commands/baseline.js.map +1 -1
  16. package/dist/cli/commands/history.cjs +2 -14
  17. package/dist/cli/commands/history.cjs.map +1 -1
  18. package/dist/cli/commands/history.d.cts.map +1 -1
  19. package/dist/cli/commands/history.d.ts.map +1 -1
  20. package/dist/cli/commands/history.js +1 -13
  21. package/dist/cli/commands/history.js.map +1 -1
  22. package/dist/cli/context.cjs +60 -0
  23. package/dist/cli/context.cjs.map +1 -0
  24. package/dist/cli/context.d.cts +28 -0
  25. package/dist/cli/context.d.cts.map +1 -0
  26. package/dist/cli/context.d.ts +28 -0
  27. package/dist/cli/context.d.ts.map +1 -0
  28. package/dist/cli/context.js +56 -0
  29. package/dist/cli/context.js.map +1 -0
  30. package/dist/cli/handlers.cjs +74 -0
  31. package/dist/cli/handlers.cjs.map +1 -0
  32. package/dist/cli/handlers.d.cts +13 -0
  33. package/dist/cli/handlers.d.cts.map +1 -0
  34. package/dist/cli/handlers.d.ts +13 -0
  35. package/dist/cli/handlers.d.ts.map +1 -0
  36. package/dist/cli/handlers.js +70 -0
  37. package/dist/cli/handlers.js.map +1 -0
  38. package/dist/cli/index.cjs +12 -724
  39. package/dist/cli/index.cjs.map +1 -1
  40. package/dist/cli/index.d.cts +4 -39
  41. package/dist/cli/index.d.cts.map +1 -1
  42. package/dist/cli/index.d.ts +4 -39
  43. package/dist/cli/index.d.ts.map +1 -1
  44. package/dist/cli/index.js +9 -722
  45. package/dist/cli/index.js.map +1 -1
  46. package/dist/cli/parsers/analyze.cjs +54 -0
  47. package/dist/cli/parsers/analyze.cjs.map +1 -0
  48. package/dist/cli/parsers/analyze.d.cts +37 -0
  49. package/dist/cli/parsers/analyze.d.cts.map +1 -0
  50. package/dist/cli/parsers/analyze.d.ts +37 -0
  51. package/dist/cli/parsers/analyze.d.ts.map +1 -0
  52. package/dist/cli/parsers/analyze.js +51 -0
  53. package/dist/cli/parsers/analyze.js.map +1 -0
  54. package/dist/cli/parsers/baseline.cjs +75 -0
  55. package/dist/cli/parsers/baseline.cjs.map +1 -0
  56. package/dist/cli/parsers/baseline.d.cts +59 -0
  57. package/dist/cli/parsers/baseline.d.cts.map +1 -0
  58. package/dist/cli/parsers/baseline.d.ts +59 -0
  59. package/dist/cli/parsers/baseline.d.ts.map +1 -0
  60. package/dist/cli/parsers/baseline.js +72 -0
  61. package/dist/cli/parsers/baseline.js.map +1 -0
  62. package/dist/cli/parsers/global.cjs +49 -0
  63. package/dist/cli/parsers/global.cjs.map +1 -0
  64. package/dist/cli/parsers/global.d.cts +45 -0
  65. package/dist/cli/parsers/global.d.cts.map +1 -0
  66. package/dist/cli/parsers/global.d.ts +45 -0
  67. package/dist/cli/parsers/global.d.ts.map +1 -0
  68. package/dist/cli/parsers/global.js +46 -0
  69. package/dist/cli/parsers/global.js.map +1 -0
  70. package/dist/cli/parsers/history.cjs +138 -0
  71. package/dist/cli/parsers/history.cjs.map +1 -0
  72. package/dist/cli/parsers/history.d.cts +108 -0
  73. package/dist/cli/parsers/history.d.cts.map +1 -0
  74. package/dist/cli/parsers/history.d.ts +108 -0
  75. package/dist/cli/parsers/history.d.ts.map +1 -0
  76. package/dist/cli/parsers/history.js +135 -0
  77. package/dist/cli/parsers/history.js.map +1 -0
  78. package/dist/cli/parsers/index.cjs +35 -0
  79. package/dist/cli/parsers/index.cjs.map +1 -0
  80. package/dist/cli/parsers/index.d.cts +15 -0
  81. package/dist/cli/parsers/index.d.cts.map +1 -0
  82. package/dist/cli/parsers/index.d.ts +15 -0
  83. package/dist/cli/parsers/index.d.ts.map +1 -0
  84. package/dist/cli/parsers/index.js +15 -0
  85. package/dist/cli/parsers/index.js.map +1 -0
  86. package/dist/cli/parsers/init.cjs +39 -0
  87. package/dist/cli/parsers/init.cjs.map +1 -0
  88. package/dist/cli/parsers/init.d.cts +32 -0
  89. package/dist/cli/parsers/init.d.cts.map +1 -0
  90. package/dist/cli/parsers/init.d.ts +32 -0
  91. package/dist/cli/parsers/init.d.ts.map +1 -0
  92. package/dist/cli/parsers/init.js +36 -0
  93. package/dist/cli/parsers/init.js.map +1 -0
  94. package/dist/cli/parsers/run.cjs +99 -0
  95. package/dist/cli/parsers/run.cjs.map +1 -0
  96. package/dist/cli/parsers/run.d.cts +62 -0
  97. package/dist/cli/parsers/run.d.cts.map +1 -0
  98. package/dist/cli/parsers/run.d.ts +62 -0
  99. package/dist/cli/parsers/run.d.ts.map +1 -0
  100. package/dist/cli/parsers/run.js +96 -0
  101. package/dist/cli/parsers/run.js.map +1 -0
  102. package/dist/cli/parsers/test.cjs +42 -0
  103. package/dist/cli/parsers/test.cjs.map +1 -0
  104. package/dist/cli/parsers/test.d.cts +31 -0
  105. package/dist/cli/parsers/test.d.cts.map +1 -0
  106. package/dist/cli/parsers/test.d.ts +31 -0
  107. package/dist/cli/parsers/test.d.ts.map +1 -0
  108. package/dist/cli/parsers/test.js +39 -0
  109. package/dist/cli/parsers/test.js.map +1 -0
  110. package/dist/cli/theme.cjs +35 -0
  111. package/dist/cli/theme.cjs.map +1 -0
  112. package/dist/cli/theme.d.cts +31 -0
  113. package/dist/cli/theme.d.cts.map +1 -0
  114. package/dist/cli/theme.d.ts +31 -0
  115. package/dist/cli/theme.d.ts.map +1 -0
  116. package/dist/cli/theme.js +32 -0
  117. package/dist/cli/theme.js.map +1 -0
  118. package/dist/errors/base.cjs +3 -12
  119. package/dist/errors/base.cjs.map +1 -1
  120. package/dist/errors/base.d.cts +0 -7
  121. package/dist/errors/base.d.cts.map +1 -1
  122. package/dist/errors/base.d.ts +0 -7
  123. package/dist/errors/base.d.ts.map +1 -1
  124. package/dist/errors/base.js +1 -9
  125. package/dist/errors/base.js.map +1 -1
  126. package/dist/services/profiler/profile-runner.cjs +11 -0
  127. package/dist/services/profiler/profile-runner.cjs.map +1 -1
  128. package/dist/services/profiler/profile-runner.d.cts +2 -0
  129. package/dist/services/profiler/profile-runner.d.cts.map +1 -1
  130. package/dist/services/profiler/profile-runner.d.ts +2 -0
  131. package/dist/services/profiler/profile-runner.d.ts.map +1 -1
  132. package/dist/services/profiler/profile-runner.js +11 -0
  133. package/dist/services/profiler/profile-runner.js.map +1 -1
  134. package/package.json +1 -1
  135. package/src/cli/builder.ts +387 -0
  136. package/src/cli/commands/baseline.ts +7 -33
  137. package/src/cli/commands/history.ts +1 -16
  138. package/src/cli/context.ts +117 -0
  139. package/src/cli/handlers.ts +76 -0
  140. package/src/cli/index.ts +10 -1012
  141. package/src/cli/parsers/analyze.ts +61 -0
  142. package/src/cli/parsers/baseline.ts +92 -0
  143. package/src/cli/parsers/global.ts +51 -0
  144. package/src/cli/parsers/history.ts +168 -0
  145. package/src/cli/parsers/index.ts +28 -0
  146. package/src/cli/parsers/init.ts +45 -0
  147. package/src/cli/parsers/run.ts +118 -0
  148. package/src/cli/parsers/test.ts +46 -0
  149. package/src/cli/theme.ts +33 -0
  150. package/src/errors/base.ts +1 -10
  151. package/src/services/profiler/profile-runner.ts +15 -0
package/dist/cli/index.js CHANGED
@@ -2,643 +2,20 @@
2
2
  /**
3
3
  * ModestBench CLI Entry Point
4
4
  *
5
- * Command-line interface using bargs for command parsing and routing. Provides
6
- * global options, help generation, and dependency injection setup.
5
+ * Command-line interface using bargs for command parsing and routing. This
6
+ * module provides the main entry points and re-exports key types.
7
7
  *
8
8
  * @packageDocumentation
9
9
  */
10
- import { ansi, bargs, BargsError, HelpError, map, merge, opt, pos, } from '@boneskull/bargs';
10
+ import { BargsError, HelpError } from '@boneskull/bargs';
11
11
  import { realpathSync } from 'node:fs';
12
12
  import { fileURLToPath } from 'node:url';
13
- import { bootstrap } from "../bootstrap.js";
14
- import { ABORT_TIMEOUT, DEFAULT_ENGINE, DEFAULT_REPORTER, Engines, ErrorCodes, ExitCodes, Reporters, } from "../constants.js";
15
- import { AccurateEngine, TinybenchEngine } from "../core/engines/index.js";
16
- import { isError } from "../errors/base.js";
17
- import { isModestBenchError, UnknownError } from "../errors/index.js";
18
- import { CsvReporter, HumanReporter, JsonReporter, NyanReporter, SimpleReporter, } from "../reporters/index.js";
19
- // Import commands
20
- import { handleAnalyzeCommand as analyzeCommand, } from "./commands/analyze.js";
21
- import { handleAnalyzeCommand as handleBaselineAnalyzeCommand, handleDeleteCommand as handleBaselineDeleteCommand, handleListCommand as handleBaselineListCommand, handleSetCommand as handleBaselineSetCommand, handleShowCommand as handleBaselineShowCommand, } from "./commands/baseline.js";
22
- import { handleCleanCommand, handleCompareCommand, handleExportCommand, handleListCommand, handleShowCommand, handleTrendsCommand, } from "./commands/history.js";
23
- import { handleInitCommand as initCommand } from "./commands/init.js";
24
- import { handleRunCommand as runCommand } from "./commands/run.js";
25
- import { handleTestCommand as testCommand, } from "./commands/test.js";
26
- // ============================================================================
27
- // Global Options Parser
28
- // ============================================================================
29
- const globalOptions = opt.options({
30
- config: opt.string({
31
- aliases: ['c'],
32
- description: 'Path to configuration file',
33
- }),
34
- cwd: opt.string({
35
- description: 'Working directory',
36
- }),
37
- json: opt.boolean({
38
- description: 'Output results in JSON format',
39
- }),
40
- 'no-color': opt.boolean({
41
- description: 'Disable colored output',
42
- }),
43
- progress: opt.boolean({
44
- description: 'Show animated progress bar',
45
- }),
46
- verbose: opt.boolean({
47
- aliases: ['v'],
48
- description: 'Enable verbose output',
49
- }),
50
- });
51
- // ============================================================================
52
- // History Command Parsers
53
- // ============================================================================
54
- const historyListParser = opt.options({
55
- format: opt.enum(['human', 'json', 'csv'], {
56
- description: 'Output format',
57
- }),
58
- limit: opt.number({
59
- description: 'Maximum number of results',
60
- }),
61
- pattern: opt.string({
62
- description: 'Filter by benchmark name pattern',
63
- }),
64
- since: opt.string({
65
- description: 'Show runs since date (ISO 8601 or relative like "1 week ago")',
66
- }),
67
- tag: opt.array('string', {
68
- aliases: ['t'],
69
- description: 'Filter by tags',
70
- }),
71
- until: opt.string({
72
- description: 'Show runs until date (ISO 8601 or relative like "1 day ago")',
73
- }),
74
- });
75
- const historyShowParser = merge(opt.options({
76
- format: opt.enum(['human', 'json', 'csv'], {
77
- description: 'Output format',
78
- }),
79
- }), pos.positionals(pos.string({
80
- description: 'ID of the benchmark run to show',
81
- name: 'run-id',
82
- required: true,
83
- })));
84
- const historyCompareParser = merge(opt.options({
85
- format: opt.enum(['human', 'json'], {
86
- description: 'Output format',
87
- }),
88
- }), pos.positionals(pos.string({
89
- description: 'ID of the first benchmark run',
90
- name: 'run-id1',
91
- required: true,
92
- }), pos.string({
93
- description: 'ID of the second benchmark run',
94
- name: 'run-id2',
95
- required: true,
96
- })));
97
- const historyTrendsParser = merge(opt.options({
98
- all: opt.boolean({
99
- aliases: ['a'],
100
- description: 'Analyze all runs (ignore limit)',
101
- }),
102
- format: opt.enum(['human', 'json'], {
103
- description: 'Output format',
104
- }),
105
- limit: opt.number({
106
- description: 'Maximum number of runs to analyze',
107
- }),
108
- since: opt.string({
109
- description: 'Show trends since date (ISO 8601 or relative like "1 week ago")',
110
- }),
111
- tag: opt.array('string', {
112
- aliases: ['t'],
113
- description: 'Filter by tags',
114
- }),
115
- until: opt.string({
116
- description: 'Show trends until date (ISO 8601 or relative like "1 day ago")',
117
- }),
118
- }), pos.positionals(pos.string({
119
- description: 'Filter by benchmark name pattern',
120
- name: 'pattern',
121
- })));
122
- const historyCleanParser = map(opt.options({
123
- 'max-age': opt.number({
124
- description: 'Remove runs older than this many days',
125
- }),
126
- 'max-runs': opt.number({
127
- description: 'Keep only this many most recent runs',
128
- }),
129
- 'max-size': opt.number({
130
- description: 'Keep history under this size in bytes',
131
- }),
132
- yes: opt.boolean({
133
- aliases: ['y'],
134
- description: 'Confirm cleanup without prompting',
135
- }),
136
- }), ({ positionals, values }) => {
137
- if (!values['max-age'] && !values['max-runs'] && !values['max-size']) {
138
- throw new Error('At least one cleanup criterion must be specified (--max-age, --max-runs, or --max-size)');
139
- }
140
- return { positionals, values };
141
- });
142
- const historyExportParser = opt.options({
143
- format: opt.enum(['json', 'csv'], {
144
- description: 'Export format',
145
- }),
146
- output: opt.string({
147
- aliases: ['o'],
148
- description: 'Output file path',
149
- required: true,
150
- }),
151
- since: opt.string({
152
- description: 'Export runs since date',
153
- }),
154
- until: opt.string({
155
- description: 'Export runs until date',
156
- }),
157
- });
158
- // ============================================================================
159
- // Baseline Command Parsers
160
- // ============================================================================
161
- const baselineSetParser = merge(opt.options({
162
- branch: opt.string({
163
- description: 'Git branch name',
164
- }),
165
- commit: opt.string({
166
- description: 'Git commit SHA (40 characters)',
167
- }),
168
- default: opt.boolean({
169
- description: 'Set as default baseline',
170
- }),
171
- 'run-id': opt.string({
172
- description: 'Specific run ID to save (default: most recent)',
173
- }),
174
- }), pos.positionals(pos.string({
175
- description: 'Name for the baseline',
176
- name: 'name',
177
- required: true,
178
- })));
179
- const baselineListParser = opt.options({
180
- format: opt.enum(['human', 'json'], {
181
- description: 'Output format',
182
- }),
183
- });
184
- const baselineShowParser = merge(opt.options({
185
- format: opt.enum(['human', 'json'], {
186
- description: 'Output format',
187
- }),
188
- }), pos.positionals(pos.string({
189
- description: 'Baseline name to show',
190
- name: 'name',
191
- required: true,
192
- })));
193
- const baselineDeleteParser = pos.positionals(pos.string({
194
- description: 'Baseline name to delete',
195
- name: 'name',
196
- required: true,
197
- }));
198
- const baselineAnalyzeParser = opt.options({
199
- confidence: opt.number({
200
- description: 'Confidence level (0.5-0.999, default 0.95)',
201
- }),
202
- runs: opt.number({
203
- description: 'Number of recent runs to analyze',
204
- }),
205
- });
206
- // ============================================================================
207
- // Run Command Parser
208
- // ============================================================================
209
- const runParserBase = merge(opt.options({
210
- bail: opt.boolean({
211
- aliases: ['b'],
212
- description: 'Stop on first failure',
213
- }),
214
- engine: opt.enum([Engines.TINYBENCH, Engines.ACCURATE], {
215
- aliases: ['e'],
216
- description: 'Benchmark engine: tinybench (default) or accurate (requires --allow-natives-syntax)',
217
- }),
218
- exclude: opt.array('string', {
219
- aliases: ['X'],
220
- description: 'Exclude patterns',
221
- }),
222
- 'exclude-tag': opt.array('string', {
223
- aliases: ['T'],
224
- description: 'Exclude benchmarks with any of these tags',
225
- }),
226
- iterations: opt.number({
227
- aliases: ['i'],
228
- description: 'Number of iterations per benchmark',
229
- }),
230
- 'json-pretty': opt.boolean({
231
- description: 'Pretty-print JSON output (only affects json reporter)',
232
- }),
233
- 'limit-by': opt.enum(['time', 'iterations', 'any', 'all'], {
234
- aliases: ['l', 'limit'],
235
- description: 'How to limit benchmarks: time (time budget), iterations (sample count), any (either threshold), all (both thresholds)',
236
- }),
237
- output: opt.string({
238
- aliases: ['o'],
239
- description: 'Output directory for reports',
240
- }),
241
- 'output-file': opt.string({
242
- aliases: ['of', 'file'],
243
- description: 'Custom filename for reporter output (use with single reporter only)',
244
- }),
245
- quiet: opt.boolean({
246
- aliases: ['q'],
247
- description: 'Minimal output',
248
- }),
249
- reporter: opt.array([
250
- Reporters.HUMAN,
251
- Reporters.JSON,
252
- Reporters.CSV,
253
- Reporters.NYAN,
254
- Reporters.SIMPLE,
255
- ], {
256
- aliases: ['r'],
257
- default: [DEFAULT_REPORTER],
258
- description: 'Output reporters to use (human,json,csv)',
259
- }),
260
- tag: opt.array('string', {
261
- description: 'Include only benchmarks with any of these tags',
262
- }),
263
- time: opt.number({
264
- aliases: ['t'],
265
- description: 'Time budget per benchmark in milliseconds',
266
- }),
267
- timeout: opt.number({
268
- description: 'Timeout per benchmark in milliseconds',
269
- }),
270
- warmup: opt.number({
271
- aliases: ['w', 'warm'],
272
- description: 'Number of warmup iterations',
273
- }),
274
- }), pos.positionals(pos.variadic('string', {
275
- description: 'File paths, directory paths, or glob patterns for benchmark files',
276
- name: 'pattern',
277
- })));
278
- // Add validation via map()
279
- const runParser = map(runParserBase, ({ positionals, values }) => {
280
- if (values.reporter && values.reporter.length > 1 && values['output-file']) {
281
- throw new Error('--output-file can only be used with a single reporter. Use --output <dir> for multiple reporters.');
282
- }
283
- return { positionals, values };
284
- });
285
- // ============================================================================
286
- // Init Command Parser
287
- // ============================================================================
288
- const initParser = merge(opt.options({
289
- 'config-type': opt.enum(['json', 'yaml', 'js', 'ts'], {
290
- description: 'Configuration file format',
291
- }),
292
- examples: opt.boolean({
293
- description: 'Include example benchmark files',
294
- }),
295
- force: opt.boolean({
296
- description: 'Overwrite existing files',
297
- }),
298
- quiet: opt.boolean({
299
- aliases: ['q'],
300
- description: 'Minimal output',
301
- }),
302
- yes: opt.boolean({
303
- aliases: ['y'],
304
- description: 'Accept all prompts automatically',
305
- }),
306
- }), pos.positionals(pos.enum(['basic', 'advanced', 'library'], {
307
- description: 'Type of project to initialize',
308
- name: 'type',
309
- })));
310
- // ============================================================================
311
- // Analyze Command Parser
312
- // ============================================================================
313
- const analyzeParserBase = merge(opt.options({
314
- 'filter-file': opt.string({
315
- description: 'Filter functions by file glob pattern',
316
- }),
317
- 'group-by-file': opt.boolean({
318
- description: 'Group results by file',
319
- }),
320
- input: opt.string({
321
- aliases: ['i'],
322
- description: 'Path to existing *.cpuprofile file',
323
- }),
324
- 'min-percent': opt.number({
325
- aliases: ['m', 'min'],
326
- default: 0.5,
327
- description: 'Minimum execution percentage to show',
328
- }),
329
- top: opt.number({
330
- aliases: ['n'],
331
- default: 25,
332
- description: 'Number of top functions to show',
333
- }),
334
- }), pos.positionals(pos.string({
335
- description: 'Command to analyze (e.g., "npm test")',
336
- name: 'command',
337
- })));
338
- // Add validation
339
- const analyzeParser = map(analyzeParserBase, ({ positionals, values }) => {
340
- const [command] = positionals;
341
- if (!command && !values.input) {
342
- throw new Error('Either [command] or --input must be provided');
343
- }
344
- return { positionals, values };
345
- });
346
- // ============================================================================
347
- // Test Command Parser
348
- // ============================================================================
349
- const testParser = merge(opt.options({
350
- bail: opt.boolean({
351
- aliases: ['b'],
352
- description: 'Stop on first failure',
353
- }),
354
- iterations: opt.number({
355
- aliases: ['i'],
356
- default: 100,
357
- description: 'Number of iterations per test',
358
- }),
359
- quiet: opt.boolean({
360
- aliases: ['q'],
361
- description: 'Minimal output',
362
- }),
363
- warmup: opt.number({
364
- aliases: ['w'],
365
- default: 5,
366
- description: 'Number of warmup iterations',
367
- }),
368
- }), pos.positionals(pos.enum(['ava', 'jest', 'mocha', 'node-test'], {
369
- description: 'Test framework to use',
370
- name: 'framework',
371
- required: true,
372
- }), pos.variadic('string', {
373
- description: 'Test file paths or glob patterns',
374
- name: 'files',
375
- })));
376
- // ============================================================================
377
- // Subcommand-specific options
378
- // ============================================================================
379
- /**
380
- * Additional global options for history and baseline subcommands
381
- */
382
- const quietOption = opt.options({
383
- quiet: opt.boolean({
384
- description: 'Minimal output',
385
- }),
386
- });
387
- // ============================================================================
388
- // Main CLI Builder
389
- // ============================================================================
390
- /**
391
- * Synthwave-inspired theme for CLI help output
392
- *
393
- * Matches the retro aesthetic used in modestbench reporters
394
- */
395
- const synthwaveTheme = {
396
- colors: {
397
- command: ansi.brightMagenta,
398
- defaultText: ansi.dim,
399
- defaultValue: ansi.brightYellow,
400
- description: ansi.brightWhite,
401
- epilog: ansi.brightWhite,
402
- example: ansi.cyan,
403
- flag: ansi.brightCyan,
404
- positional: ansi.brightMagenta,
405
- scriptName: ansi.brightCyan + ansi.bold,
406
- sectionHeader: ansi.magenta + ansi.bold,
407
- type: ansi.brightWhite + ansi.dim,
408
- url: ansi.brightCyan + ansi.underline,
409
- usage: ansi.white,
410
- },
411
- };
412
- const createCli = (abortController) => {
413
- return bargs('modestbench', {
414
- description: 'A modern benchmark runner for Node.js',
415
- theme: synthwaveTheme,
416
- })
417
- .globals(globalOptions)
418
- .command('run', runParser, async ({ positionals, values }) => {
419
- const [pattern] = positionals;
420
- const context = await createCliContext(values, abortController, values.engine);
421
- const exitCode = await runCommand(context, {
422
- bail: values.bail,
423
- config: values.config,
424
- cwd: values.cwd,
425
- engine: values.engine,
426
- exclude: values.exclude,
427
- excludeTags: values['exclude-tag'],
428
- iterations: values.iterations,
429
- json: values.json,
430
- jsonPretty: values['json-pretty'],
431
- noColor: values['no-color'],
432
- outputDir: values.output,
433
- outputFile: values['output-file'],
434
- pattern,
435
- progress: values.progress,
436
- quiet: values.quiet,
437
- reporters: values.reporter,
438
- tags: values.tag,
439
- time: values.time,
440
- timeout: values.timeout,
441
- verbose: values.verbose,
442
- warmup: values.warmup,
443
- });
444
- process.exit(exitCode);
445
- }, 'Run benchmark files')
446
- .command('history', (history) => history
447
- .globals(quietOption)
448
- .command('list', historyListParser, async ({ values }) => {
449
- const context = await createCliContext(values, abortController);
450
- const exitCode = await handleListCommand(context, {
451
- cwd: values.cwd,
452
- format: values.format,
453
- limit: values.limit,
454
- pattern: values.pattern,
455
- since: values.since,
456
- tags: values.tag,
457
- until: values.until,
458
- verbose: values.verbose,
459
- });
460
- process.exit(exitCode);
461
- }, 'List recent benchmark runs')
462
- .command('show', historyShowParser, async ({ positionals, values }) => {
463
- const [runId] = positionals;
464
- const context = await createCliContext(values, abortController);
465
- const exitCode = await handleShowCommand(context, {
466
- cwd: values.cwd,
467
- format: values.format,
468
- runId,
469
- verbose: values.verbose,
470
- });
471
- process.exit(exitCode);
472
- }, 'Show detailed results for a specific run')
473
- .command('compare', historyCompareParser, async ({ positionals, values }) => {
474
- const [runId1, runId2] = positionals;
475
- const context = await createCliContext(values, abortController);
476
- const exitCode = await handleCompareCommand(context, {
477
- cwd: values.cwd,
478
- format: values.format,
479
- runId1,
480
- runId2,
481
- verbose: values.verbose,
482
- });
483
- process.exit(exitCode);
484
- }, 'Compare two benchmark runs')
485
- .command('trends', historyTrendsParser, async ({ positionals, values }) => {
486
- const [pattern] = positionals;
487
- const context = await createCliContext(values, abortController);
488
- const exitCode = await handleTrendsCommand(context, {
489
- all: values.all,
490
- cwd: values.cwd,
491
- format: values.format,
492
- limit: values.limit,
493
- pattern,
494
- since: values.since,
495
- tags: values.tag,
496
- until: values.until,
497
- verbose: values.verbose,
498
- });
499
- process.exit(exitCode);
500
- }, 'Show performance trends over time')
501
- .command('clean', historyCleanParser, async ({ values }) => {
502
- const context = await createCliContext(values, abortController);
503
- const exitCode = await handleCleanCommand(context, {
504
- confirm: values.yes,
505
- cwd: values.cwd,
506
- maxAge: values['max-age'],
507
- maxRuns: values['max-runs'],
508
- maxSize: values['max-size'],
509
- quiet: values.quiet,
510
- verbose: values.verbose,
511
- });
512
- process.exit(exitCode);
513
- }, 'Clean up old benchmark history')
514
- .command('export', historyExportParser, async ({ values }) => {
515
- const context = await createCliContext(values, abortController);
516
- const exitCode = await handleExportCommand(context, {
517
- cwd: values.cwd,
518
- format: values.format,
519
- outputPath: values.output,
520
- quiet: Boolean(values.quiet),
521
- since: values.since,
522
- until: values.until,
523
- verbose: values.verbose,
524
- });
525
- process.exitCode = exitCode;
526
- }, 'Export benchmark history to a file'), 'View and manage benchmark history')
527
- .command('baseline', (baseline) => baseline
528
- .globals(quietOption)
529
- .command('set', baselineSetParser, async ({ positionals, values }) => {
530
- const [name] = positionals;
531
- const context = await createCliContext(values, abortController);
532
- const exitCode = await handleBaselineSetCommand(context, {
533
- branch: values.branch,
534
- commit: values.commit,
535
- cwd: values.cwd,
536
- default: values.default,
537
- name,
538
- quiet: Boolean(values.quiet),
539
- runId: values['run-id'],
540
- verbose: values.verbose,
541
- });
542
- process.exit(exitCode);
543
- }, 'Save a benchmark run as a baseline')
544
- .command('list', baselineListParser, async ({ values }) => {
545
- const context = await createCliContext(values, abortController);
546
- const exitCode = await handleBaselineListCommand(context, {
547
- cwd: values.cwd,
548
- format: values.format,
549
- quiet: Boolean(values.quiet),
550
- verbose: values.verbose,
551
- });
552
- process.exit(exitCode);
553
- }, 'List all saved baselines')
554
- .command('show', baselineShowParser, async ({ positionals, values }) => {
555
- const [name] = positionals;
556
- const context = await createCliContext(values, abortController);
557
- const exitCode = await handleBaselineShowCommand(context, {
558
- cwd: values.cwd,
559
- format: values.format,
560
- name,
561
- quiet: Boolean(values.quiet),
562
- verbose: values.verbose,
563
- });
564
- process.exit(exitCode);
565
- }, 'Show baseline details')
566
- .command('delete', baselineDeleteParser, async ({ positionals, values }) => {
567
- const [name] = positionals;
568
- const context = await createCliContext(values, abortController);
569
- const exitCode = await handleBaselineDeleteCommand(context, {
570
- cwd: values.cwd,
571
- name,
572
- quiet: Boolean(values.quiet),
573
- verbose: values.verbose,
574
- });
575
- process.exit(exitCode);
576
- }, 'Delete a baseline')
577
- .command('analyze', baselineAnalyzeParser, async ({ values }) => {
578
- const context = await createCliContext(values, abortController);
579
- const exitCode = await handleBaselineAnalyzeCommand(context, {
580
- confidence: values.confidence,
581
- cwd: values.cwd,
582
- quiet: Boolean(values.quiet),
583
- runs: values.runs,
584
- verbose: values.verbose,
585
- });
586
- process.exit(exitCode);
587
- }, 'Analyze history and suggest performance budgets'), 'Manage performance baselines')
588
- .command('init', initParser, async ({ positionals, values }) => {
589
- const [type] = positionals;
590
- const context = await createCliContext(values, abortController);
591
- const exitCode = await initCommand(context, {
592
- configType: values['config-type'],
593
- cwd: values.cwd,
594
- examples: values.examples,
595
- force: values.force,
596
- quiet: values.quiet,
597
- type,
598
- verbose: values.verbose,
599
- yes: values.yes,
600
- });
601
- process.exitCode = exitCode;
602
- }, 'Initialize a new benchmark project')
603
- .command('analyze', analyzeParser, async ({ positionals, values }) => {
604
- const [command] = positionals;
605
- // Context not needed for analyze command currently
606
- const context = {};
607
- const options = {
608
- color: !values['no-color'],
609
- command,
610
- cwd: values.cwd || process.cwd(),
611
- filterFile: values['filter-file'],
612
- groupByFile: values['group-by-file'],
613
- input: values.input,
614
- minPercent: values['min-percent'],
615
- top: values.top,
616
- };
617
- process.exitCode = await analyzeCommand(context, options);
618
- }, {
619
- aliases: ['profile'],
620
- description: 'Analyze code execution and identify benchmark candidates',
621
- })
622
- .command('test', testParser, async ({ positionals, values }) => {
623
- const [framework, files] = positionals;
624
- const context = await createCliContext(values, abortController);
625
- const options = {
626
- bail: values.bail,
627
- cwd: values.cwd,
628
- framework,
629
- iterations: values.iterations,
630
- json: values.json,
631
- noColor: values['no-color'],
632
- pattern: files,
633
- quiet: values.quiet,
634
- verbose: values.verbose,
635
- warmup: values.warmup,
636
- };
637
- const exitCode = await testCommand(context, options);
638
- process.exit(exitCode);
639
- }, 'Run test files as benchmarks')
640
- .defaultCommand('run');
641
- };
13
+ import { ErrorCodes, ExitCodes } from "../constants.js";
14
+ import { isModestBenchError } from "../errors/index.js";
15
+ import { createCli } from "./builder.js";
16
+ import { setupSignalHandlers } from "./handlers.js";
17
+ // Re-export types and utilities for external use
18
+ export { createCliContext } from "./context.js";
642
19
  /**
643
20
  * Initialize and run the CLI
644
21
  */
@@ -696,87 +73,6 @@ export const main = async (argv, abortController) => {
696
73
  process.exit(ExitCodes.UNKNOWN_ERROR);
697
74
  }
698
75
  };
699
- /**
700
- * Create CLI context with dependency injection
701
- */
702
- const createCliContext = async (options, abortController, engineType = DEFAULT_ENGINE) => {
703
- try {
704
- const dependencies = bootstrap();
705
- // Select engine based on type
706
- const engine = engineType === Engines.ACCURATE
707
- ? new AccurateEngine(dependencies)
708
- : new TinybenchEngine(dependencies);
709
- // Register built-in reporters
710
- engine.registerReporter(Reporters.HUMAN, new HumanReporter({
711
- color: !options['no-color'],
712
- verbose: options.verbose,
713
- }));
714
- engine.registerReporter('json', new JsonReporter({
715
- prettyPrint: false,
716
- }));
717
- engine.registerReporter('csv', new CsvReporter({
718
- includeHeaders: true,
719
- includeMetadata: true,
720
- }));
721
- engine.registerReporter('simple', new SimpleReporter({
722
- verbose: options.verbose,
723
- }));
724
- engine.registerReporter('nyan', new NyanReporter({
725
- color: !options['no-color'],
726
- }));
727
- return {
728
- abortController,
729
- configManager: engine.configManager,
730
- engine,
731
- historyStorage: engine.historyStorage,
732
- options,
733
- progressManager: engine.progressManager,
734
- reporterRegistry: engine.reporterRegistry,
735
- };
736
- }
737
- catch (error) {
738
- console.error('Failed to initialize ModestBench:', error instanceof Error ? error.message : String(error));
739
- process.exit(ExitCodes.CONFIG_ERROR);
740
- }
741
- };
742
- /**
743
- * Handle process signals gracefully
744
- */
745
- const setupSignalHandlers = (abortController) => {
746
- let abortRequested = false;
747
- const handleSignal = (signal) => {
748
- if (abortRequested) {
749
- // Second signal, force exit
750
- console.log(`\nReceived ${signal} again, forcing exit...`);
751
- process.exit(computeExitCode(signal));
752
- }
753
- console.log(`\nReceived ${signal}, aborting benchmarks...`);
754
- abortRequested = true;
755
- abortController.abort();
756
- // Give a short grace period for cleanup, then exit
757
- setTimeout(() => {
758
- console.log('\nBenchmark aborted.');
759
- process.exit(computeExitCode(signal));
760
- }, ABORT_TIMEOUT);
761
- };
762
- process
763
- .once('SIGINT', handleSignal)
764
- .once('SIGQUIT', handleSignal)
765
- .once('SIGTERM', handleSignal)
766
- .once('uncaughtException', (error) => {
767
- // Wrap non-ModestBench errors with UnknownError
768
- const wrappedError = isModestBenchError(error)
769
- ? error
770
- : new UnknownError(error.message, { cause: error });
771
- console.error(`${wrappedError}`);
772
- process.exit(ExitCodes.RUNTIME_ERROR);
773
- })
774
- .once('unhandledRejection', (reason) => {
775
- const wrappedError = new UnknownError(isError(reason) ? reason.message : String(reason), { cause: reason });
776
- console.error(`${wrappedError}`);
777
- process.exit(ExitCodes.RUNTIME_ERROR);
778
- });
779
- };
780
76
  // Run CLI if this file is executed directly
781
77
  const scriptPath = fileURLToPath(import.meta.url);
782
78
  const argPath = process.argv[1];
@@ -794,13 +90,4 @@ catch {
794
90
  cli();
795
91
  }
796
92
  }
797
- /**
798
- * Compute the exit code based on the signal
799
- *
800
- * @param signal - The signal that caused the exit
801
- * @returns The exit code
802
- */
803
- const computeExitCode = (signal) => {
804
- return 128 + (signal === 'SIGINT' ? 2 : signal === 'SIGQUIT' ? 3 : 15);
805
- };
806
93
  //# sourceMappingURL=index.js.map