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.
- package/CHANGELOG.md +25 -0
- package/README.md +37 -4
- package/dist/adapters/types.d.cts +1 -1
- package/dist/adapters/types.d.cts.map +1 -1
- package/dist/adapters/types.d.ts +1 -1
- package/dist/adapters/types.d.ts.map +1 -1
- package/dist/cli/commands/run.cjs +93 -49
- package/dist/cli/commands/run.cjs.map +1 -1
- package/dist/cli/commands/run.d.cts +1 -0
- package/dist/cli/commands/run.d.cts.map +1 -1
- package/dist/cli/commands/run.d.ts +1 -0
- package/dist/cli/commands/run.d.ts.map +1 -1
- package/dist/cli/commands/run.js +95 -51
- package/dist/cli/commands/run.js.map +1 -1
- package/dist/cli/index.cjs +7 -1
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.d.cts.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +7 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/{core → config}/benchmark-schema.cjs +1 -1
- package/dist/config/benchmark-schema.cjs.map +1 -0
- package/dist/config/benchmark-schema.d.cts +913 -0
- package/dist/config/benchmark-schema.d.cts.map +1 -0
- package/dist/config/benchmark-schema.d.ts +913 -0
- package/dist/config/benchmark-schema.d.ts.map +1 -0
- package/dist/{core → config}/benchmark-schema.js +1 -1
- package/dist/config/benchmark-schema.js.map +1 -0
- package/dist/config/schema.cjs +188 -105
- package/dist/config/schema.cjs.map +1 -1
- package/dist/config/schema.d.cts +208 -80
- package/dist/config/schema.d.cts.map +1 -1
- package/dist/config/schema.d.ts +208 -80
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +187 -104
- package/dist/config/schema.js.map +1 -1
- package/dist/constants.cjs +2 -0
- package/dist/constants.cjs.map +1 -1
- package/dist/constants.d.cts +2 -0
- package/dist/constants.d.cts.map +1 -1
- package/dist/constants.d.ts +2 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +2 -0
- package/dist/constants.js.map +1 -1
- package/dist/core/engine.cjs +50 -45
- package/dist/core/engine.cjs.map +1 -1
- package/dist/core/engine.d.cts.map +1 -1
- package/dist/core/engine.d.ts.map +1 -1
- package/dist/core/engine.js +50 -45
- package/dist/core/engine.js.map +1 -1
- package/dist/core/output-path-resolver.cjs +15 -1
- package/dist/core/output-path-resolver.cjs.map +1 -1
- package/dist/core/output-path-resolver.d.cts +8 -0
- package/dist/core/output-path-resolver.d.cts.map +1 -1
- package/dist/core/output-path-resolver.d.ts +8 -0
- package/dist/core/output-path-resolver.d.ts.map +1 -1
- package/dist/core/output-path-resolver.js +13 -0
- package/dist/core/output-path-resolver.js.map +1 -1
- package/dist/errors/index.cjs +3 -1
- package/dist/errors/index.cjs.map +1 -1
- package/dist/errors/index.d.cts +1 -1
- package/dist/errors/index.d.cts.map +1 -1
- package/dist/errors/index.d.ts +1 -1
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/index.js +1 -1
- package/dist/errors/index.js.map +1 -1
- package/dist/errors/reporter.cjs +45 -1
- package/dist/errors/reporter.cjs.map +1 -1
- package/dist/errors/reporter.d.cts +32 -0
- package/dist/errors/reporter.d.cts.map +1 -1
- package/dist/errors/reporter.d.ts +32 -0
- package/dist/errors/reporter.d.ts.map +1 -1
- package/dist/errors/reporter.js +42 -0
- package/dist/errors/reporter.js.map +1 -1
- package/dist/index.cjs +16 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/reporters/json.cjs +1 -1
- package/dist/reporters/json.cjs.map +1 -1
- package/dist/reporters/json.js +1 -1
- package/dist/reporters/json.js.map +1 -1
- package/dist/schema/modestbench-config.schema.json +94 -87
- package/dist/services/budget-evaluator.cjs +8 -6
- package/dist/services/budget-evaluator.cjs.map +1 -1
- package/dist/services/budget-evaluator.d.cts +2 -2
- package/dist/services/budget-evaluator.d.cts.map +1 -1
- package/dist/services/budget-evaluator.d.ts +2 -2
- package/dist/services/budget-evaluator.d.ts.map +1 -1
- package/dist/services/budget-evaluator.js +8 -6
- package/dist/services/budget-evaluator.js.map +1 -1
- package/dist/services/budget-resolver.cjs +214 -0
- package/dist/services/budget-resolver.cjs.map +1 -0
- package/dist/services/budget-resolver.d.cts +98 -0
- package/dist/services/budget-resolver.d.cts.map +1 -0
- package/dist/services/budget-resolver.d.ts +98 -0
- package/dist/services/budget-resolver.d.ts.map +1 -0
- package/dist/services/budget-resolver.js +203 -0
- package/dist/services/budget-resolver.js.map +1 -0
- package/dist/services/file-loader.cjs +1 -1
- package/dist/services/file-loader.cjs.map +1 -1
- package/dist/services/file-loader.js +1 -1
- package/dist/services/file-loader.js.map +1 -1
- package/dist/services/reporter-loader.cjs +281 -0
- package/dist/services/reporter-loader.cjs.map +1 -0
- package/dist/services/reporter-loader.d.cts +67 -0
- package/dist/services/reporter-loader.d.cts.map +1 -0
- package/dist/services/reporter-loader.d.ts +67 -0
- package/dist/services/reporter-loader.d.ts.map +1 -0
- package/dist/services/reporter-loader.js +241 -0
- package/dist/services/reporter-loader.js.map +1 -0
- package/dist/types/budgets.d.cts +31 -0
- package/dist/types/budgets.d.cts.map +1 -1
- package/dist/types/budgets.d.ts +31 -0
- package/dist/types/budgets.d.ts.map +1 -1
- package/dist/types/core.cjs.map +1 -1
- package/dist/types/core.d.cts +28 -75
- package/dist/types/core.d.cts.map +1 -1
- package/dist/types/core.d.ts +28 -75
- package/dist/types/core.d.ts.map +1 -1
- package/dist/types/core.js.map +1 -1
- package/dist/types/index.cjs.map +1 -1
- package/dist/types/index.d.cts +1 -0
- package/dist/types/index.d.cts.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types/plugin.cjs +9 -0
- package/dist/types/plugin.cjs.map +1 -0
- package/dist/types/plugin.d.cts +179 -0
- package/dist/types/plugin.d.cts.map +1 -0
- package/dist/types/plugin.d.ts +179 -0
- package/dist/types/plugin.d.ts.map +1 -0
- package/dist/types/plugin.js +8 -0
- package/dist/types/plugin.js.map +1 -0
- package/dist/utils/package.cjs +66 -5
- package/dist/utils/package.cjs.map +1 -1
- package/dist/utils/package.d.cts +6 -0
- package/dist/utils/package.d.cts.map +1 -1
- package/dist/utils/package.d.ts +6 -0
- package/dist/utils/package.d.ts.map +1 -1
- package/dist/utils/package.js +31 -1
- package/dist/utils/package.js.map +1 -1
- package/dist/utils/reporter-utils.cjs +90 -0
- package/dist/utils/reporter-utils.cjs.map +1 -0
- package/dist/utils/reporter-utils.d.cts +42 -0
- package/dist/utils/reporter-utils.d.cts.map +1 -0
- package/dist/utils/reporter-utils.d.ts +42 -0
- package/dist/utils/reporter-utils.d.ts.map +1 -0
- package/dist/utils/reporter-utils.js +83 -0
- package/dist/utils/reporter-utils.js.map +1 -0
- package/package.json +20 -9
- package/src/adapters/types.ts +1 -1
- package/src/cli/commands/run.ts +140 -69
- package/src/cli/index.ts +8 -1
- package/src/{core → config}/benchmark-schema.ts +1 -1
- package/src/config/schema.ts +379 -302
- package/src/constants.ts +2 -0
- package/src/core/engine.ts +74 -69
- package/src/core/output-path-resolver.ts +14 -0
- package/src/errors/index.ts +2 -0
- package/src/errors/reporter.ts +55 -0
- package/src/index.ts +19 -1
- package/src/reporters/json.ts +1 -1
- package/src/services/budget-evaluator.ts +13 -9
- package/src/services/budget-resolver.ts +254 -0
- package/src/services/file-loader.ts +1 -1
- package/src/services/reporter-loader.ts +323 -0
- package/src/types/budgets.ts +38 -0
- package/src/types/core.ts +64 -99
- package/src/types/index.ts +3 -0
- package/src/types/plugin.ts +197 -0
- package/src/utils/package.ts +32 -1
- package/src/utils/reporter-utils.ts +85 -0
- package/dist/core/benchmark-schema.cjs.map +0 -1
- package/dist/core/benchmark-schema.d.cts +0 -139
- package/dist/core/benchmark-schema.d.cts.map +0 -1
- package/dist/core/benchmark-schema.d.ts +0 -139
- package/dist/core/benchmark-schema.d.ts.map +0 -1
- 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
|
+
};
|
package/src/types/budgets.ts
CHANGED
|
@@ -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
|
-
*
|
|
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
|
-
*
|
|
299
|
-
*
|
|
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
|
|
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 */
|
package/src/types/index.ts
CHANGED
|
@@ -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
|
|