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
@@ -0,0 +1,323 @@
1
+ /**
2
+ * ModestBench Reporter Loader
3
+ *
4
+ * Service for loading third-party reporter plugins from file paths or npm
5
+ * packages. Supports multiple export patterns: plain objects, classes, and
6
+ * factory functions (sync or async).
7
+ */
8
+
9
+ import { isAbsolute, resolve } from 'node:path';
10
+ import { pathToFileURL } from 'node:url';
11
+
12
+ import type { Logger, Reporter, ReporterContext } from '../types/index.js';
13
+
14
+ import { Reporters } from '../constants.js';
15
+ import {
16
+ ReporterLoadError,
17
+ ReporterValidationError,
18
+ } from '../errors/reporter.js';
19
+ import { getPackageVersion } from '../utils/package.js';
20
+ import { reporterUtils } from '../utils/reporter-utils.js';
21
+
22
+ /**
23
+ * Current plugin API version
24
+ *
25
+ * Increment this when making breaking changes to the plugin API.
26
+ */
27
+ export const PLUGIN_API_VERSION = 1;
28
+
29
+ /**
30
+ * Set of built-in reporter names
31
+ */
32
+ const BUILT_IN_REPORTERS = new Set(Object.values(Reporters));
33
+
34
+ /**
35
+ * Required methods that all reporters must implement
36
+ */
37
+ const REQUIRED_REPORTER_METHODS = [
38
+ 'onStart',
39
+ 'onEnd',
40
+ 'onError',
41
+ 'onTaskResult',
42
+ ] as const;
43
+
44
+ /**
45
+ * Default logger implementation using console
46
+ *
47
+ * This provides a simple console-based logger for reporter plugins.
48
+ */
49
+ const defaultLogger: Logger = {
50
+ debug: (message, ...args) => console.debug(message, ...args),
51
+ error: (message, ...args) => console.error(message, ...args),
52
+ info: (message, ...args) => console.info(message, ...args),
53
+ trace: (message, ...args) => console.trace(message, ...args),
54
+ warn: (message, ...args) => console.warn(message, ...args),
55
+ };
56
+
57
+ /**
58
+ * Create a ReporterContext for passing to plugins
59
+ *
60
+ * @param logger - Optional logger to use (defaults to console-based logger)
61
+ * @returns ReporterContext with version info and utilities
62
+ */
63
+ export const createReporterContext = (logger?: Logger): ReporterContext => {
64
+ return {
65
+ logger: logger ?? defaultLogger,
66
+ pluginApiVersion: PLUGIN_API_VERSION,
67
+ utils: reporterUtils,
68
+ version: getPackageVersion(),
69
+ };
70
+ };
71
+
72
+ /**
73
+ * Get the list of missing required methods from a reporter object
74
+ *
75
+ * @param obj - Object to check
76
+ * @returns Array of missing method names
77
+ */
78
+ const getMissingMethods = (obj: unknown): string[] => {
79
+ if (typeof obj !== 'object' || obj === null) {
80
+ return [...REQUIRED_REPORTER_METHODS];
81
+ }
82
+
83
+ return REQUIRED_REPORTER_METHODS.filter(
84
+ (method) => typeof (obj as Record<string, unknown>)[method] !== 'function',
85
+ );
86
+ };
87
+
88
+ /**
89
+ * Check if a specifier refers to a built-in reporter
90
+ *
91
+ * @param specifier - Reporter name or path
92
+ * @returns True if the specifier is a built-in reporter name
93
+ */
94
+ export const isBuiltInReporter = (specifier: string): boolean => {
95
+ return BUILT_IN_REPORTERS.has(
96
+ specifier as (typeof Reporters)[keyof typeof Reporters],
97
+ );
98
+ };
99
+
100
+ /**
101
+ * Check if a function is a class constructor
102
+ *
103
+ * Uses heuristics to distinguish classes from regular functions:
104
+ *
105
+ * - Classes have a non-writable prototype property
106
+ * - Class syntax produces different toString() output
107
+ *
108
+ * @param func - Function to check
109
+ * @returns True if the function appears to be a class constructor
110
+ */
111
+ const isClass = (
112
+ func: unknown,
113
+ ): func is new (...args: unknown[]) => unknown => {
114
+ if (typeof func !== 'function') {
115
+ return false;
116
+ }
117
+
118
+ // Classes have a non-writable prototype
119
+ const protoDescriptor = Object.getOwnPropertyDescriptor(func, 'prototype');
120
+ if (!protoDescriptor || protoDescriptor.writable) {
121
+ return false;
122
+ }
123
+
124
+ // Check if it uses class syntax (handles both 'class Foo' and 'class{')
125
+ const funcStr = func.toString();
126
+ return /^class\b/.test(funcStr);
127
+ };
128
+
129
+ /**
130
+ * Check if a specifier looks like a file path
131
+ *
132
+ * @param specifier - Reporter name or path
133
+ * @returns True if the specifier appears to be a file path
134
+ */
135
+ export const isFilePath = (specifier: string): boolean => {
136
+ return (
137
+ specifier.startsWith('.') ||
138
+ specifier.startsWith('/') ||
139
+ // isAbsolute handles Windows paths like 'C:\path\to\file.js'
140
+ isAbsolute(specifier)
141
+ );
142
+ };
143
+
144
+ /**
145
+ * Check if an object implements the Reporter interface
146
+ *
147
+ * Validates that all required methods are present and are functions.
148
+ *
149
+ * @param obj - Object to validate
150
+ * @returns True if the object has all required reporter methods
151
+ */
152
+ const isReporterObject = (obj: unknown): obj is Reporter => {
153
+ if (typeof obj !== 'object' || obj === null) {
154
+ return false;
155
+ }
156
+
157
+ return REQUIRED_REPORTER_METHODS.every(
158
+ (method) => typeof (obj as Record<string, unknown>)[method] === 'function',
159
+ );
160
+ };
161
+
162
+ /**
163
+ * Load a reporter from a file path or npm package name
164
+ *
165
+ * Supports multiple export patterns:
166
+ *
167
+ * 1. Plain Reporter object (simplest, no options support)
168
+ * 2. Class constructor (instantiated with options and context)
169
+ * 3. Factory function (called with options and context, can be async)
170
+ *
171
+ * @example
172
+ *
173
+ * ```typescript
174
+ * // Load from file path
175
+ * const reporter = await loadReporter('./my-reporter.js', {
176
+ * verbose: true,
177
+ * });
178
+ *
179
+ * // Load from npm package
180
+ * const reporter = await loadReporter('@company/custom-reporter', {
181
+ * apiKey: 'xxx',
182
+ * });
183
+ * ```
184
+ *
185
+ * @param specifier - File path (relative or absolute) or npm package name
186
+ * @param options - Options to pass to the reporter factory/constructor
187
+ * @param cwd - Current working directory for resolving relative paths
188
+ * @returns Loaded reporter instance
189
+ * @throws ReporterLoadError if the module cannot be loaded
190
+ * @throws ReporterValidationError if the module doesn't implement Reporter
191
+ */
192
+ export const loadReporter = async (
193
+ specifier: string,
194
+ options: Record<string, unknown> = {},
195
+ cwd: string = process.cwd(),
196
+ ): Promise<Reporter> => {
197
+ const context = createReporterContext();
198
+ const resolvedSpecifier = resolveSpecifier(specifier, cwd);
199
+
200
+ let module: unknown;
201
+
202
+ try {
203
+ module = await import(resolvedSpecifier);
204
+ } catch (error) {
205
+ const message = error instanceof Error ? error.message : String(error);
206
+ throw new ReporterLoadError(message, specifier, { cause: error });
207
+ }
208
+
209
+ // Handle ESM/CJS interop - get default export if present
210
+ const exported = (module as { default?: unknown }).default ?? module;
211
+
212
+ // Case 1: Already a Reporter object (plain object export)
213
+ if (isReporterObject(exported)) {
214
+ return exported;
215
+ }
216
+
217
+ // Case 2: Class constructor
218
+ if (isClass(exported)) {
219
+ let instance: unknown;
220
+
221
+ try {
222
+ instance = new (exported as new (
223
+ options: Record<string, unknown>,
224
+ context: ReporterContext,
225
+ ) => unknown)(options, context);
226
+ } catch (error) {
227
+ const message = error instanceof Error ? error.message : String(error);
228
+ throw new ReporterLoadError(
229
+ `Constructor threw error: ${message}`,
230
+ specifier,
231
+ { cause: error },
232
+ );
233
+ }
234
+
235
+ validateReporter(instance, specifier);
236
+ return instance;
237
+ }
238
+
239
+ // Case 3: Factory function (sync or async)
240
+ if (typeof exported === 'function') {
241
+ let result: unknown;
242
+
243
+ try {
244
+ result = await (
245
+ exported as (
246
+ options: Record<string, unknown>,
247
+ context: ReporterContext,
248
+ ) => Promise<unknown>
249
+ )(options, context);
250
+ } catch (error) {
251
+ const message = error instanceof Error ? error.message : String(error);
252
+ throw new ReporterLoadError(
253
+ `Factory function threw error: ${message}`,
254
+ specifier,
255
+ { cause: error },
256
+ );
257
+ }
258
+
259
+ validateReporter(result, specifier);
260
+ return result;
261
+ }
262
+
263
+ // None of the above - could be an object with missing methods or invalid type
264
+ if (typeof exported === 'object' && exported !== null) {
265
+ // It's an object but missing required methods
266
+ const missing = getMissingMethods(exported);
267
+ throw new ReporterValidationError(
268
+ 'Module does not implement Reporter interface.',
269
+ specifier,
270
+ missing,
271
+ );
272
+ }
273
+
274
+ // Completely invalid export type
275
+ throw new ReporterValidationError(
276
+ 'Module must export a Reporter object, class, or factory function.',
277
+ specifier,
278
+ );
279
+ };
280
+
281
+ /**
282
+ * Resolve a specifier to an importable URL or module name
283
+ *
284
+ * @param specifier - File path or npm package name
285
+ * @param cwd - Current working directory for resolving relative paths
286
+ * @returns Resolved module specifier
287
+ */
288
+ const resolveSpecifier = (specifier: string, cwd: string): string => {
289
+ if (isFilePath(specifier)) {
290
+ const absolutePath = resolve(cwd, specifier);
291
+ return pathToFileURL(absolutePath).href;
292
+ }
293
+
294
+ // npm package name - return as-is for dynamic import
295
+ return specifier;
296
+ };
297
+
298
+ /**
299
+ * Validate that an object implements the Reporter interface
300
+ *
301
+ * @param obj - Object to validate
302
+ * @param specifier - Original specifier for error messages
303
+ * @throws ReporterValidationError if validation fails
304
+ */
305
+ /**
306
+ * Type signature for the validateReporter assertion function
307
+ */
308
+ type ValidateReporterFn = (
309
+ obj: unknown,
310
+ specifier: string,
311
+ ) => asserts obj is Reporter;
312
+
313
+ const validateReporter: ValidateReporterFn = (obj, specifier) => {
314
+ const missing = getMissingMethods(obj);
315
+
316
+ if (missing.length > 0) {
317
+ throw new ReporterValidationError(
318
+ 'Module does not implement Reporter interface.',
319
+ specifier,
320
+ missing,
321
+ );
322
+ }
323
+ };
@@ -80,6 +80,30 @@ export interface Budget {
80
80
  readonly relative?: RelativeBudget;
81
81
  }
82
82
 
83
+ /**
84
+ * A budget pattern with glob support for files and simple wildcards for
85
+ * suite/task
86
+ *
87
+ * File patterns use minimatch glob syntax (e.g., `**\/*.bench.js`). Suite/task
88
+ * patterns use simple `*` wildcard (matches any value).
89
+ */
90
+ export interface BudgetPattern {
91
+ /** The budget to apply when this pattern matches */
92
+ readonly budget: Budget;
93
+
94
+ /** Glob pattern for file matching (minimatch syntax) */
95
+ readonly filePattern: string;
96
+
97
+ /** Computed specificity score (higher = more specific) */
98
+ readonly specificity: number;
99
+
100
+ /** Suite name or `*` for wildcard */
101
+ readonly suitePattern: string;
102
+
103
+ /** Task name or `*` for wildcard */
104
+ readonly taskPattern: string;
105
+ }
106
+
83
107
  /**
84
108
  * Budget evaluation result for a single task
85
109
  */
@@ -159,6 +183,20 @@ export interface RelativeBudget {
159
183
  readonly maxRegression?: number;
160
184
  }
161
185
 
186
+ /**
187
+ * Resolved budgets structure with exact matches and patterns separated
188
+ *
189
+ * During evaluation, exact matches are checked first, then patterns are matched
190
+ * in order of specificity (highest first).
191
+ */
192
+ export interface ResolvedBudgets {
193
+ /** Exact TaskId matches (no wildcards or globs) */
194
+ readonly exact: Record<string, Budget>;
195
+
196
+ /** Patterns with wildcards/globs, sorted by specificity descending */
197
+ readonly patterns: readonly BudgetPattern[];
198
+ }
199
+
162
200
  /**
163
201
  * Branded type for benchmark run identifiers
164
202
  *
package/src/types/core.ts CHANGED
@@ -1,3 +1,11 @@
1
+ import type { z } from 'zod';
2
+
3
+ import type {
4
+ jsonReporterConfigSchema,
5
+ ModestBenchConfig,
6
+ ModestBenchConfigInput,
7
+ reporterConfigSchema,
8
+ } from '../config/schema.js';
1
9
  // Budget-related types
2
10
  import type {
3
11
  AbsoluteBudget,
@@ -5,38 +13,16 @@ import type {
5
13
  BaselineStorage,
6
14
  BaselineSummaryData,
7
15
  Budget,
16
+ BudgetPattern,
8
17
  BudgetResult,
9
18
  BudgetSummary,
10
19
  BudgetViolation,
11
20
  RelativeBudget,
21
+ ResolvedBudgets,
12
22
  RunId,
13
23
  TaskId,
14
24
  } from './budgets.js';
15
25
 
16
- export type {
17
- AbsoluteBudget,
18
- BaselineReference,
19
- BaselineStorage,
20
- BaselineSummaryData,
21
- Budget,
22
- BudgetResult,
23
- BudgetSummary,
24
- BudgetViolation,
25
- RelativeBudget,
26
- RunId,
27
- TaskId,
28
- };
29
-
30
- // Re-export schema-derived types
31
- export type {
32
- BenchmarkDefinition,
33
- BenchmarkDefinitionInput,
34
- BenchmarkSuite,
35
- BenchmarkSuiteInput,
36
- BenchmarkTask,
37
- BenchmarkTaskInput,
38
- } from '../core/benchmark-schema.js';
39
-
40
26
  /**
41
27
  * Benchmark file structure after parsing
42
28
  */
@@ -51,21 +37,6 @@ export interface BenchmarkFile {
51
37
  readonly metadata: FileMetadata;
52
38
  }
53
39
 
54
- /**
55
- * ModestBench Core Types
56
- *
57
- * Defines the fundamental data structures used throughout the ModestBench
58
- * system. These types represent benchmark results, metadata, configuration, and
59
- * system state.
60
- *
61
- * Note: BenchmarkDefinition, BenchmarkSuite, and BenchmarkTask types are
62
- * derived from Zod schemas and re-exported from benchmark-schema.ts for type
63
- * safety and consistency.
64
- */
65
-
66
- // Re-export identifier helper functions
67
- export { createRunId, createTaskId } from '../utils/identifiers.js';
68
-
69
40
  /**
70
41
  * Represents a complete benchmark run across multiple files
71
42
  */
@@ -98,6 +69,34 @@ export interface BenchmarkRun {
98
69
  readonly tags?: string[];
99
70
  }
100
71
 
72
+ export type {
73
+ AbsoluteBudget,
74
+ BaselineReference,
75
+ BaselineStorage,
76
+ BaselineSummaryData,
77
+ Budget,
78
+ BudgetPattern,
79
+ BudgetResult,
80
+ BudgetSummary,
81
+ BudgetViolation,
82
+ RelativeBudget,
83
+ ResolvedBudgets,
84
+ RunId,
85
+ TaskId,
86
+ };
87
+
88
+ export type { ModestBenchConfig, ModestBenchConfigInput };
89
+
90
+ // Re-export schema-derived types
91
+ export type {
92
+ BenchmarkDefinition,
93
+ BenchmarkDefinitionInput,
94
+ BenchmarkSuite,
95
+ BenchmarkSuiteInput,
96
+ BenchmarkTask,
97
+ BenchmarkTaskInput,
98
+ } from '../config/benchmark-schema.js';
99
+
101
100
  /**
102
101
  * CI/CD environment information
103
102
  */
@@ -116,6 +115,21 @@ export interface CiInfo {
116
115
  readonly pullRequest?: string;
117
116
  }
118
117
 
118
+ /**
119
+ * ModestBench Core Types
120
+ *
121
+ * Defines the fundamental data structures used throughout the ModestBench
122
+ * system. These types represent benchmark results, metadata, configuration, and
123
+ * system state.
124
+ *
125
+ * Note: BenchmarkDefinition, BenchmarkSuite, and BenchmarkTask types are
126
+ * derived from Zod schemas and re-exported from benchmark-schema.ts for type
127
+ * safety and consistency.
128
+ */
129
+
130
+ // Re-export identifier helper functions
131
+ export { createRunId, createTaskId } from '../utils/identifiers.js';
132
+
119
133
  /**
120
134
  * CPU information
121
135
  */
@@ -277,6 +291,11 @@ export interface GitInfo {
277
291
  readonly timestamp: Date;
278
292
  }
279
293
 
294
+ /**
295
+ * Configuration options for the JSON reporter
296
+ */
297
+ export type JsonReporterConfig = z.infer<typeof jsonReporterConfigSchema>;
298
+
280
299
  /**
281
300
  * Memory information
282
301
  */
@@ -290,68 +309,12 @@ export interface MemoryInfo {
290
309
  }
291
310
 
292
311
  /**
293
- * Benchmark configuration
294
- *
295
- * The JSON Schema for this configuration is available at
296
- * `dist/schema/modestbench-config.schema.json` after building the project.
312
+ * Reporter-specific configuration
297
313
  *
298
- * Config files can optionally include a `$schema` property pointing to the
299
- * schema file for IDE autocomplete and validation support.
300
- *
301
- * @example
302
- *
303
- * ```json
304
- * {
305
- * "$schema": "./node_modules/modestbench/dist/schema/modestbench-config.schema.json",
306
- * "iterations": 1000,
307
- * "reporters": ["human", "json"],
308
- * "time": 5000
309
- * }
310
- * ```
314
+ * Provides typed configuration for known reporters. Unknown reporter configs
315
+ * are allowed via index signature.
311
316
  */
312
- export interface ModestBenchConfig {
313
- readonly $schema?: string | undefined;
314
- /** Whether to stop on first failure */
315
- readonly bail: boolean;
316
- /** Name of baseline to use for relative budget comparisons */
317
- readonly baseline?: string | undefined;
318
- /** How to handle budget violations: 'fail', 'warn', or 'report' */
319
- readonly budgetMode?: 'fail' | 'report' | 'warn' | undefined;
320
- /** Performance budgets mapped by task identifier */
321
- readonly budgets?: Record<string, unknown> | undefined;
322
- /** Patterns to exclude from discovery */
323
- readonly exclude: string[];
324
- /** Tags to exclude from execution */
325
- readonly excludeTags: string[];
326
- /** Default number of iterations per task */
327
- readonly iterations: number;
328
- /** How to limit benchmark execution: 'time', 'iterations', 'any', or 'all' */
329
- readonly limitBy: 'all' | 'any' | 'iterations' | 'time';
330
- /** Custom metadata to attach to runs */
331
- readonly metadata: Record<string, unknown>;
332
- /** Output directory for reports (undefined means stdout for data reporters) */
333
- readonly outputDir?: string;
334
- /** Pattern(s) for discovering benchmark files */
335
- readonly pattern: string | string[];
336
- /** Whether to run in quiet mode */
337
- readonly quiet: boolean;
338
- /** Configuration for specific reporters */
339
- readonly reporterConfig: Record<string, unknown>;
340
- /** Reporters to use for output */
341
- readonly reporters: string[];
342
- /** Tags to include (if empty, include all) */
343
- readonly tags: string[];
344
- /** Threshold configuration for performance assertions */
345
- readonly thresholds: ThresholdConfig;
346
- /** Maximum time to spend on each task in milliseconds */
347
- readonly time: number;
348
- /** Timeout for individual tasks in milliseconds */
349
- readonly timeout: number;
350
- /** Whether to run in verbose mode */
351
- readonly verbose: boolean;
352
- /** Number of warmup iterations before measurement */
353
- readonly warmup: number;
354
- }
317
+ export type ReporterConfig = z.infer<typeof reporterConfigSchema>;
355
318
 
356
319
  /**
357
320
  * Summary statistics for a benchmark run
@@ -441,6 +404,8 @@ export interface TaskResult {
441
404
 
442
405
  /**
443
406
  * Threshold configuration for performance assertions
407
+ *
408
+ * TODO: Derive from thresholdConfigSchema via z.infer (see #15)
444
409
  */
445
410
  export interface ThresholdConfig {
446
411
  /** Maximum allowed margin of error percentage */
@@ -13,6 +13,9 @@ export { createRunId, createTaskId } from './core.js';
13
13
  // Interface contracts
14
14
  export type * from './interfaces.js';
15
15
 
16
+ // Plugin types (for third-party reporter authors)
17
+ export type * from './plugin.js';
18
+
16
19
  // Profiler types
17
20
  export type * from './profiler.js';
18
21