modestbench 0.7.0 → 0.9.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 (184) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +37 -4
  3. package/dist/adapters/types.d.cts +1 -1
  4. package/dist/adapters/types.d.cts.map +1 -1
  5. package/dist/adapters/types.d.ts +1 -1
  6. package/dist/adapters/types.d.ts.map +1 -1
  7. package/dist/cli/commands/run.cjs +93 -49
  8. package/dist/cli/commands/run.cjs.map +1 -1
  9. package/dist/cli/commands/run.d.cts +1 -0
  10. package/dist/cli/commands/run.d.cts.map +1 -1
  11. package/dist/cli/commands/run.d.ts +1 -0
  12. package/dist/cli/commands/run.d.ts.map +1 -1
  13. package/dist/cli/commands/run.js +95 -51
  14. package/dist/cli/commands/run.js.map +1 -1
  15. package/dist/cli/index.cjs +7 -1
  16. package/dist/cli/index.cjs.map +1 -1
  17. package/dist/cli/index.d.cts.map +1 -1
  18. package/dist/cli/index.d.ts.map +1 -1
  19. package/dist/cli/index.js +7 -1
  20. package/dist/cli/index.js.map +1 -1
  21. package/dist/{core → config}/benchmark-schema.cjs +1 -1
  22. package/dist/config/benchmark-schema.cjs.map +1 -0
  23. package/dist/config/benchmark-schema.d.cts +913 -0
  24. package/dist/config/benchmark-schema.d.cts.map +1 -0
  25. package/dist/config/benchmark-schema.d.ts +913 -0
  26. package/dist/config/benchmark-schema.d.ts.map +1 -0
  27. package/dist/{core → config}/benchmark-schema.js +1 -1
  28. package/dist/config/benchmark-schema.js.map +1 -0
  29. package/dist/config/schema.cjs +188 -105
  30. package/dist/config/schema.cjs.map +1 -1
  31. package/dist/config/schema.d.cts +208 -80
  32. package/dist/config/schema.d.cts.map +1 -1
  33. package/dist/config/schema.d.ts +208 -80
  34. package/dist/config/schema.d.ts.map +1 -1
  35. package/dist/config/schema.js +187 -104
  36. package/dist/config/schema.js.map +1 -1
  37. package/dist/constants.cjs +2 -0
  38. package/dist/constants.cjs.map +1 -1
  39. package/dist/constants.d.cts +2 -0
  40. package/dist/constants.d.cts.map +1 -1
  41. package/dist/constants.d.ts +2 -0
  42. package/dist/constants.d.ts.map +1 -1
  43. package/dist/constants.js +2 -0
  44. package/dist/constants.js.map +1 -1
  45. package/dist/core/engine.cjs +50 -45
  46. package/dist/core/engine.cjs.map +1 -1
  47. package/dist/core/engine.d.cts.map +1 -1
  48. package/dist/core/engine.d.ts.map +1 -1
  49. package/dist/core/engine.js +50 -45
  50. package/dist/core/engine.js.map +1 -1
  51. package/dist/core/output-path-resolver.cjs +15 -1
  52. package/dist/core/output-path-resolver.cjs.map +1 -1
  53. package/dist/core/output-path-resolver.d.cts +8 -0
  54. package/dist/core/output-path-resolver.d.cts.map +1 -1
  55. package/dist/core/output-path-resolver.d.ts +8 -0
  56. package/dist/core/output-path-resolver.d.ts.map +1 -1
  57. package/dist/core/output-path-resolver.js +13 -0
  58. package/dist/core/output-path-resolver.js.map +1 -1
  59. package/dist/errors/index.cjs +3 -1
  60. package/dist/errors/index.cjs.map +1 -1
  61. package/dist/errors/index.d.cts +1 -1
  62. package/dist/errors/index.d.cts.map +1 -1
  63. package/dist/errors/index.d.ts +1 -1
  64. package/dist/errors/index.d.ts.map +1 -1
  65. package/dist/errors/index.js +1 -1
  66. package/dist/errors/index.js.map +1 -1
  67. package/dist/errors/reporter.cjs +45 -1
  68. package/dist/errors/reporter.cjs.map +1 -1
  69. package/dist/errors/reporter.d.cts +32 -0
  70. package/dist/errors/reporter.d.cts.map +1 -1
  71. package/dist/errors/reporter.d.ts +32 -0
  72. package/dist/errors/reporter.d.ts.map +1 -1
  73. package/dist/errors/reporter.js +42 -0
  74. package/dist/errors/reporter.js.map +1 -1
  75. package/dist/index.cjs +16 -1
  76. package/dist/index.cjs.map +1 -1
  77. package/dist/index.d.cts +3 -1
  78. package/dist/index.d.cts.map +1 -1
  79. package/dist/index.d.ts +3 -1
  80. package/dist/index.d.ts.map +1 -1
  81. package/dist/index.js +5 -1
  82. package/dist/index.js.map +1 -1
  83. package/dist/reporters/json.cjs +1 -1
  84. package/dist/reporters/json.cjs.map +1 -1
  85. package/dist/reporters/json.js +1 -1
  86. package/dist/reporters/json.js.map +1 -1
  87. package/dist/schema/modestbench-config.schema.json +94 -87
  88. package/dist/services/budget-evaluator.cjs +8 -6
  89. package/dist/services/budget-evaluator.cjs.map +1 -1
  90. package/dist/services/budget-evaluator.d.cts +2 -2
  91. package/dist/services/budget-evaluator.d.cts.map +1 -1
  92. package/dist/services/budget-evaluator.d.ts +2 -2
  93. package/dist/services/budget-evaluator.d.ts.map +1 -1
  94. package/dist/services/budget-evaluator.js +8 -6
  95. package/dist/services/budget-evaluator.js.map +1 -1
  96. package/dist/services/budget-resolver.cjs +214 -0
  97. package/dist/services/budget-resolver.cjs.map +1 -0
  98. package/dist/services/budget-resolver.d.cts +98 -0
  99. package/dist/services/budget-resolver.d.cts.map +1 -0
  100. package/dist/services/budget-resolver.d.ts +98 -0
  101. package/dist/services/budget-resolver.d.ts.map +1 -0
  102. package/dist/services/budget-resolver.js +203 -0
  103. package/dist/services/budget-resolver.js.map +1 -0
  104. package/dist/services/file-loader.cjs +1 -1
  105. package/dist/services/file-loader.cjs.map +1 -1
  106. package/dist/services/file-loader.js +1 -1
  107. package/dist/services/file-loader.js.map +1 -1
  108. package/dist/services/reporter-loader.cjs +281 -0
  109. package/dist/services/reporter-loader.cjs.map +1 -0
  110. package/dist/services/reporter-loader.d.cts +67 -0
  111. package/dist/services/reporter-loader.d.cts.map +1 -0
  112. package/dist/services/reporter-loader.d.ts +67 -0
  113. package/dist/services/reporter-loader.d.ts.map +1 -0
  114. package/dist/services/reporter-loader.js +241 -0
  115. package/dist/services/reporter-loader.js.map +1 -0
  116. package/dist/types/budgets.d.cts +31 -0
  117. package/dist/types/budgets.d.cts.map +1 -1
  118. package/dist/types/budgets.d.ts +31 -0
  119. package/dist/types/budgets.d.ts.map +1 -1
  120. package/dist/types/core.cjs.map +1 -1
  121. package/dist/types/core.d.cts +28 -75
  122. package/dist/types/core.d.cts.map +1 -1
  123. package/dist/types/core.d.ts +28 -75
  124. package/dist/types/core.d.ts.map +1 -1
  125. package/dist/types/core.js.map +1 -1
  126. package/dist/types/index.cjs.map +1 -1
  127. package/dist/types/index.d.cts +1 -0
  128. package/dist/types/index.d.cts.map +1 -1
  129. package/dist/types/index.d.ts +1 -0
  130. package/dist/types/index.d.ts.map +1 -1
  131. package/dist/types/index.js.map +1 -1
  132. package/dist/types/plugin.cjs +9 -0
  133. package/dist/types/plugin.cjs.map +1 -0
  134. package/dist/types/plugin.d.cts +179 -0
  135. package/dist/types/plugin.d.cts.map +1 -0
  136. package/dist/types/plugin.d.ts +179 -0
  137. package/dist/types/plugin.d.ts.map +1 -0
  138. package/dist/types/plugin.js +8 -0
  139. package/dist/types/plugin.js.map +1 -0
  140. package/dist/utils/package.cjs +66 -5
  141. package/dist/utils/package.cjs.map +1 -1
  142. package/dist/utils/package.d.cts +6 -0
  143. package/dist/utils/package.d.cts.map +1 -1
  144. package/dist/utils/package.d.ts +6 -0
  145. package/dist/utils/package.d.ts.map +1 -1
  146. package/dist/utils/package.js +31 -1
  147. package/dist/utils/package.js.map +1 -1
  148. package/dist/utils/reporter-utils.cjs +90 -0
  149. package/dist/utils/reporter-utils.cjs.map +1 -0
  150. package/dist/utils/reporter-utils.d.cts +42 -0
  151. package/dist/utils/reporter-utils.d.cts.map +1 -0
  152. package/dist/utils/reporter-utils.d.ts +42 -0
  153. package/dist/utils/reporter-utils.d.ts.map +1 -0
  154. package/dist/utils/reporter-utils.js +83 -0
  155. package/dist/utils/reporter-utils.js.map +1 -0
  156. package/package.json +20 -9
  157. package/src/adapters/types.ts +1 -1
  158. package/src/cli/commands/run.ts +140 -69
  159. package/src/cli/index.ts +8 -1
  160. package/src/{core → config}/benchmark-schema.ts +1 -1
  161. package/src/config/schema.ts +379 -302
  162. package/src/constants.ts +2 -0
  163. package/src/core/engine.ts +74 -69
  164. package/src/core/output-path-resolver.ts +14 -0
  165. package/src/errors/index.ts +2 -0
  166. package/src/errors/reporter.ts +55 -0
  167. package/src/index.ts +19 -1
  168. package/src/reporters/json.ts +1 -1
  169. package/src/services/budget-evaluator.ts +13 -9
  170. package/src/services/budget-resolver.ts +254 -0
  171. package/src/services/file-loader.ts +1 -1
  172. package/src/services/reporter-loader.ts +323 -0
  173. package/src/types/budgets.ts +38 -0
  174. package/src/types/core.ts +64 -99
  175. package/src/types/index.ts +3 -0
  176. package/src/types/plugin.ts +197 -0
  177. package/src/utils/package.ts +32 -1
  178. package/src/utils/reporter-utils.ts +85 -0
  179. package/dist/core/benchmark-schema.cjs.map +0 -1
  180. package/dist/core/benchmark-schema.d.cts +0 -139
  181. package/dist/core/benchmark-schema.d.cts.map +0 -1
  182. package/dist/core/benchmark-schema.d.ts +0 -139
  183. package/dist/core/benchmark-schema.d.ts.map +0 -1
  184. package/dist/core/benchmark-schema.js.map +0 -1
@@ -7,14 +7,24 @@
7
7
 
8
8
  import { resolve } from 'node:path';
9
9
 
10
- import type { BenchmarkRun, ModestBenchConfig } from '../../types/index.js';
10
+ import type {
11
+ BenchmarkRun,
12
+ ModestBenchConfig,
13
+ Reporter,
14
+ ReporterConfig,
15
+ } from '../../types/index.js';
11
16
  import type { CliContext } from '../index.js';
12
17
 
13
18
  import { ErrorCodes, ExitCodes } from '../../constants.js';
14
- import { resolveOutputPath } from '../../core/output-path-resolver.js';
19
+ import {
20
+ generateTimestampedFilename,
21
+ resolveOutputPath,
22
+ } from '../../core/output-path-resolver.js';
15
23
  import {
16
24
  type BudgetExceededError,
17
25
  InvalidArgumentError,
26
+ ReporterLoadError,
27
+ ReporterValidationError,
18
28
  UnknownReporterError,
19
29
  } from '../../errors/index.js';
20
30
  import { CsvReporter } from '../../reporters/csv.js';
@@ -22,6 +32,11 @@ import { HumanReporter } from '../../reporters/human.js';
22
32
  import { JsonReporter } from '../../reporters/json.js';
23
33
  import { NyanReporter } from '../../reporters/nyan.js';
24
34
  import { SimpleReporter } from '../../reporters/simple.js';
35
+ import {
36
+ isBuiltInReporter,
37
+ isFilePath,
38
+ loadReporter,
39
+ } from '../../services/reporter-loader.js';
25
40
  import { hasErrorCode, isError } from '../../utils/type-guards.js';
26
41
 
27
42
  /**
@@ -50,6 +65,7 @@ interface RunOptions {
50
65
  excludeTags?: string[] | undefined;
51
66
  iterations?: number | undefined;
52
67
  json?: boolean | undefined;
68
+ jsonPretty?: boolean | undefined;
53
69
  noColor?: boolean | undefined;
54
70
  outputDir?: string | undefined;
55
71
  outputFile?: string | undefined;
@@ -106,7 +122,7 @@ export const handleRunCommand = async (
106
122
  if (showCliMessages) {
107
123
  console.error('Setting up reporters...');
108
124
  }
109
- const reporters = setupReporters(
125
+ const reporters = await setupReporters(
110
126
  context,
111
127
  config,
112
128
  verbose,
@@ -115,6 +131,7 @@ export const handleRunCommand = async (
115
131
  options.outputDir,
116
132
  options.outputFile,
117
133
  options.progress,
134
+ options.jsonPretty,
118
135
  );
119
136
 
120
137
  // Step 3: Discovery phase
@@ -370,19 +387,27 @@ const loadConfiguration = async (context: CliContext, options: RunOptions) => {
370
387
 
371
388
  /**
372
389
  * Setup and configure reporters based on configuration
390
+ *
391
+ * Supports built-in reporters, registry-based custom reporters, and external
392
+ * reporters loaded from file paths or npm packages.
373
393
  */
374
- const setupReporters = (
394
+ const setupReporters = async (
375
395
  context: CliContext,
376
- config: { outputDir?: string; reporters?: string[] },
396
+ config: {
397
+ outputDir?: string;
398
+ reporterConfig?: ReporterConfig;
399
+ reporters?: string[];
400
+ },
377
401
  isVerbose: boolean,
378
402
  showCliMessages: boolean,
379
403
  explicitQuiet: boolean,
380
404
  explicitOutputDir?: string,
381
405
  explicitOutputFile?: string,
382
406
  progressOption?: boolean,
383
- ) => {
407
+ explicitJsonPretty?: boolean,
408
+ ): Promise<Reporter[]> => {
384
409
  try {
385
- const reporters = [];
410
+ const reporters: Reporter[] = [];
386
411
  // Dedupe requested reporters
387
412
  const requestedReporters = [...new Set(config.reporters || ['human'])];
388
413
 
@@ -398,67 +423,108 @@ const setupReporters = (
398
423
  const builtInReporters = ['human', 'json', 'csv', 'nyan', 'simple'];
399
424
 
400
425
  for (const reporterName of requestedReporters) {
401
- let reporter;
402
-
403
- // Create reporter instances with output path configuration
404
- switch (reporterName) {
405
- case 'csv': {
406
- const outputPath = resolveOutputPath(
407
- outputDir,
408
- explicitOutputFile,
409
- 'results.csv',
410
- );
411
- reporter = new CsvReporter({
412
- includeHeaders: true,
413
- includeMetadata: true,
414
- ...(outputPath ? { outputPath } : {}),
415
- quiet: explicitQuiet, // Only applies explicit --quiet flag; CSV output can coexist with progress messages on different streams
416
- verbose: isVerbose,
417
- });
418
- break;
419
- }
426
+ let reporter: Reporter;
427
+
428
+ // Check if this is a built-in reporter
429
+ if (isBuiltInReporter(reporterName)) {
430
+ // Create reporter instances with output path configuration
431
+ switch (reporterName) {
432
+ case 'csv': {
433
+ const outputPath = resolveOutputPath(
434
+ outputDir,
435
+ explicitOutputFile,
436
+ generateTimestampedFilename('csv'),
437
+ );
438
+ reporter = new CsvReporter({
439
+ includeHeaders: true,
440
+ includeMetadata: true,
441
+ ...(outputPath ? { outputPath } : {}),
442
+ quiet: explicitQuiet,
443
+ verbose: isVerbose,
444
+ });
445
+ break;
446
+ }
420
447
 
421
- case 'human':
422
- reporter = new HumanReporter({
423
- color: true,
424
- progress: progressOption ?? true,
425
- quiet: explicitQuiet, // Only applies explicit --quiet flag; JSON reporter forcing quiet mode does not affect HumanReporter progress output
426
- verbose: isVerbose,
427
- });
428
- break;
429
-
430
- case 'json': {
431
- const outputPath = resolveOutputPath(
432
- outputDir,
433
- explicitOutputFile,
434
- 'results.json',
435
- );
436
- reporter = new JsonReporter({
437
- ...(outputPath ? { outputPath } : {}),
438
- prettyPrint: true,
439
- });
440
- break;
441
- }
448
+ case 'human':
449
+ reporter = new HumanReporter({
450
+ color: true,
451
+ progress: progressOption ?? true,
452
+ quiet: explicitQuiet,
453
+ verbose: isVerbose,
454
+ });
455
+ break;
456
+
457
+ case 'json': {
458
+ const outputPath = resolveOutputPath(
459
+ outputDir,
460
+ explicitOutputFile,
461
+ generateTimestampedFilename('json'),
462
+ );
463
+ // Precedence: CLI flag > config file > default (false)
464
+ const prettyPrint =
465
+ explicitJsonPretty ??
466
+ config.reporterConfig?.json?.prettyPrint ??
467
+ false;
468
+ reporter = new JsonReporter({
469
+ ...(outputPath ? { outputPath } : {}),
470
+ prettyPrint,
471
+ });
472
+ break;
473
+ }
442
474
 
443
- case 'nyan':
444
- reporter = new NyanReporter({
445
- color: true,
446
- quiet: explicitQuiet,
447
- });
448
- break;
449
-
450
- case 'simple':
451
- reporter = new SimpleReporter({
452
- quiet: explicitQuiet,
453
- verbose: isVerbose,
454
- });
455
- break;
456
-
457
- default:
458
- // Fall back to registry for custom reporters
459
- reporter = context.reporterRegistry.get(reporterName);
460
- if (!reporter) {
461
- // Combine built-in reporters with registered custom reporters
475
+ case 'nyan':
476
+ reporter = new NyanReporter({
477
+ color: true,
478
+ quiet: explicitQuiet,
479
+ });
480
+ break;
481
+
482
+ case 'simple':
483
+ reporter = new SimpleReporter({
484
+ quiet: explicitQuiet,
485
+ verbose: isVerbose,
486
+ });
487
+ break;
488
+
489
+ default:
490
+ // TypeScript exhaustiveness check - should never reach here
491
+ throw new Error(`Unhandled built-in reporter: ${reporterName}`);
492
+ }
493
+ } else if (isFilePath(reporterName)) {
494
+ // External reporter from file path
495
+ const reporterOptions =
496
+ (config.reporterConfig?.[reporterName] as Record<string, unknown>) ??
497
+ {};
498
+ if (showCliMessages) {
499
+ console.error(`Loading external reporter: ${reporterName}`);
500
+ }
501
+ reporter = await loadReporter(reporterName, reporterOptions);
502
+ } else {
503
+ // Try registry first, then npm package
504
+ const registryReporter = context.reporterRegistry.get(reporterName);
505
+ if (registryReporter) {
506
+ reporter = registryReporter;
507
+ } else {
508
+ // Try loading as npm package
509
+ const reporterOptions =
510
+ (config.reporterConfig?.[reporterName] as Record<
511
+ string,
512
+ unknown
513
+ >) ?? {};
514
+ if (showCliMessages) {
515
+ console.error(`Loading reporter package: ${reporterName}`);
516
+ }
517
+ try {
518
+ reporter = await loadReporter(reporterName, reporterOptions);
519
+ } catch (error) {
520
+ // If loading fails and it's not a file path, provide helpful error
521
+ if (
522
+ error instanceof ReporterLoadError ||
523
+ error instanceof ReporterValidationError
524
+ ) {
525
+ throw error;
526
+ }
527
+ // Combine built-in reporters with registered custom reporters for error message
462
528
  const registeredReporters = Object.keys(
463
529
  context.reporterRegistry.getAll(),
464
530
  );
@@ -467,10 +533,11 @@ const setupReporters = (
467
533
  ...registeredReporters,
468
534
  ];
469
535
  throw new UnknownReporterError(
470
- `Unknown reporter: ${reporterName}. Available: ${availableReporters.join(', ')}`,
536
+ `Unknown reporter: ${reporterName}. Available built-in reporters: ${availableReporters.join(', ')}. ` +
537
+ `For external reporters, use a file path (./my-reporter.js) or npm package name.`,
471
538
  );
472
539
  }
473
- break;
540
+ }
474
541
  }
475
542
 
476
543
  reporters.push(reporter);
@@ -484,7 +551,11 @@ const setupReporters = (
484
551
  } catch (error) {
485
552
  // Re-throw our custom errors
486
553
  const errorCode = hasErrorCode(error) ? error.code : undefined;
487
- if (errorCode === ErrorCodes.REPORTER_UNKNOWN) {
554
+ if (
555
+ errorCode === ErrorCodes.REPORTER_UNKNOWN ||
556
+ errorCode === ErrorCodes.REPORTER_LOAD_FAILED ||
557
+ errorCode === ErrorCodes.REPORTER_INVALID
558
+ ) {
488
559
  throw error;
489
560
  }
490
561
  throw new InvalidArgumentError(
package/src/cli/index.ts CHANGED
@@ -276,6 +276,12 @@ export const main = async (
276
276
  'Benchmark engine: tinybench (default) or accurate (requires --allow-natives-syntax)',
277
277
  type: 'string',
278
278
  })
279
+ .option('json-pretty', {
280
+ defaultDescription: 'false',
281
+ description:
282
+ 'Pretty-print JSON output (only affects json reporter)',
283
+ type: 'boolean',
284
+ })
279
285
  .example([
280
286
  ['$0 run', 'Run benchmarks in current directory and bench/'],
281
287
  ['$0 run benchmarks/', 'Run all benchmarks in a directory'],
@@ -315,6 +321,7 @@ export const main = async (
315
321
  excludeTags: argv['exclude-tag'],
316
322
  iterations: argv.iterations,
317
323
  json: argv.json,
324
+ jsonPretty: argv['json-pretty'],
318
325
  noColor: argv.noColor,
319
326
  outputDir: argv.output,
320
327
  outputFile: argv['output-file'],
@@ -1133,7 +1140,7 @@ const createCliContext = async (
1133
1140
  engine.registerReporter(
1134
1141
  'json',
1135
1142
  new JsonReporter({
1136
- prettyPrint: true,
1143
+ prettyPrint: false,
1137
1144
  }),
1138
1145
  );
1139
1146
 
@@ -11,7 +11,7 @@ import { z } from 'zod';
11
11
 
12
12
  import type { ModestBenchConfig } from '../types/core.js';
13
13
 
14
- import { partialModestBenchConfigSchema } from '../config/schema.js';
14
+ import { partialModestBenchConfigSchema } from './schema.js';
15
15
 
16
16
  /**
17
17
  * Schema for benchmark functions