modestbench 0.0.1
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 +45 -0
- package/LICENSE.md +55 -0
- package/README.md +699 -0
- package/dist/bootstrap.cjs +37 -0
- package/dist/bootstrap.cjs.map +1 -0
- package/dist/bootstrap.d.cts +17 -0
- package/dist/bootstrap.d.cts.map +1 -0
- package/dist/bootstrap.d.ts +17 -0
- package/dist/bootstrap.d.ts.map +1 -0
- package/dist/bootstrap.js +33 -0
- package/dist/bootstrap.js.map +1 -0
- package/dist/cli/commands/history.cjs +459 -0
- package/dist/cli/commands/history.cjs.map +1 -0
- package/dist/cli/commands/history.d.cts +34 -0
- package/dist/cli/commands/history.d.cts.map +1 -0
- package/dist/cli/commands/history.d.ts +34 -0
- package/dist/cli/commands/history.d.ts.map +1 -0
- package/dist/cli/commands/history.js +422 -0
- package/dist/cli/commands/history.js.map +1 -0
- package/dist/cli/commands/init.cjs +566 -0
- package/dist/cli/commands/init.cjs.map +1 -0
- package/dist/cli/commands/init.d.cts +26 -0
- package/dist/cli/commands/init.d.cts.map +1 -0
- package/dist/cli/commands/init.d.ts +26 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +562 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/run.cjs +285 -0
- package/dist/cli/commands/run.cjs.map +1 -0
- package/dist/cli/commands/run.d.cts +37 -0
- package/dist/cli/commands/run.d.cts.map +1 -0
- package/dist/cli/commands/run.d.ts +37 -0
- package/dist/cli/commands/run.d.ts.map +1 -0
- package/dist/cli/commands/run.js +248 -0
- package/dist/cli/commands/run.js.map +1 -0
- package/dist/cli/index.cjs +523 -0
- package/dist/cli/index.cjs.map +1 -0
- package/dist/cli/index.d.cts +58 -0
- package/dist/cli/index.d.cts.map +1 -0
- package/dist/cli/index.d.ts +58 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +515 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/config/manager.cjs +370 -0
- package/dist/config/manager.cjs.map +1 -0
- package/dist/config/manager.d.cts +46 -0
- package/dist/config/manager.d.cts.map +1 -0
- package/dist/config/manager.d.ts +46 -0
- package/dist/config/manager.d.ts.map +1 -0
- package/dist/config/manager.js +333 -0
- package/dist/config/manager.js.map +1 -0
- package/dist/config/schema.cjs +182 -0
- package/dist/config/schema.cjs.map +1 -0
- package/dist/config/schema.d.cts +51 -0
- package/dist/config/schema.d.cts.map +1 -0
- package/dist/config/schema.d.ts +51 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +145 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/constants.cjs +22 -0
- package/dist/constants.cjs.map +1 -0
- package/dist/constants.d.cts +10 -0
- package/dist/constants.d.cts.map +1 -0
- package/dist/constants.d.ts +10 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +19 -0
- package/dist/constants.js.map +1 -0
- package/dist/core/benchmark-schema.cjs +135 -0
- package/dist/core/benchmark-schema.cjs.map +1 -0
- package/dist/core/benchmark-schema.d.cts +139 -0
- package/dist/core/benchmark-schema.d.cts.map +1 -0
- package/dist/core/benchmark-schema.d.ts +139 -0
- package/dist/core/benchmark-schema.d.ts.map +1 -0
- package/dist/core/benchmark-schema.js +132 -0
- package/dist/core/benchmark-schema.js.map +1 -0
- package/dist/core/engine.cjs +669 -0
- package/dist/core/engine.cjs.map +1 -0
- package/dist/core/engine.d.cts +128 -0
- package/dist/core/engine.d.cts.map +1 -0
- package/dist/core/engine.d.ts +128 -0
- package/dist/core/engine.d.ts.map +1 -0
- package/dist/core/engine.js +632 -0
- package/dist/core/engine.js.map +1 -0
- package/dist/core/engines/accurate-engine.cjs +292 -0
- package/dist/core/engines/accurate-engine.cjs.map +1 -0
- package/dist/core/engines/accurate-engine.d.cts +63 -0
- package/dist/core/engines/accurate-engine.d.cts.map +1 -0
- package/dist/core/engines/accurate-engine.d.ts +63 -0
- package/dist/core/engines/accurate-engine.d.ts.map +1 -0
- package/dist/core/engines/accurate-engine.js +288 -0
- package/dist/core/engines/accurate-engine.js.map +1 -0
- package/dist/core/engines/index.cjs +21 -0
- package/dist/core/engines/index.cjs.map +1 -0
- package/dist/core/engines/index.d.cts +16 -0
- package/dist/core/engines/index.d.cts.map +1 -0
- package/dist/core/engines/index.d.ts +16 -0
- package/dist/core/engines/index.d.ts.map +1 -0
- package/dist/core/engines/index.js +16 -0
- package/dist/core/engines/index.js.map +1 -0
- package/dist/core/engines/tinybench-engine.cjs +286 -0
- package/dist/core/engines/tinybench-engine.cjs.map +1 -0
- package/dist/core/engines/tinybench-engine.d.cts +18 -0
- package/dist/core/engines/tinybench-engine.d.cts.map +1 -0
- package/dist/core/engines/tinybench-engine.d.ts +18 -0
- package/dist/core/engines/tinybench-engine.d.ts.map +1 -0
- package/dist/core/engines/tinybench-engine.js +282 -0
- package/dist/core/engines/tinybench-engine.js.map +1 -0
- package/dist/core/error-manager.cjs +303 -0
- package/dist/core/error-manager.cjs.map +1 -0
- package/dist/core/error-manager.d.cts +77 -0
- package/dist/core/error-manager.d.cts.map +1 -0
- package/dist/core/error-manager.d.ts +77 -0
- package/dist/core/error-manager.d.ts.map +1 -0
- package/dist/core/error-manager.js +299 -0
- package/dist/core/error-manager.js.map +1 -0
- package/dist/core/loader.cjs +287 -0
- package/dist/core/loader.cjs.map +1 -0
- package/dist/core/loader.d.cts +55 -0
- package/dist/core/loader.d.cts.map +1 -0
- package/dist/core/loader.d.ts +55 -0
- package/dist/core/loader.d.ts.map +1 -0
- package/dist/core/loader.js +250 -0
- package/dist/core/loader.js.map +1 -0
- package/dist/core/stats-utils.cjs +99 -0
- package/dist/core/stats-utils.cjs.map +1 -0
- package/dist/core/stats-utils.d.cts +50 -0
- package/dist/core/stats-utils.d.cts.map +1 -0
- package/dist/core/stats-utils.d.ts +50 -0
- package/dist/core/stats-utils.d.ts.map +1 -0
- package/dist/core/stats-utils.js +94 -0
- package/dist/core/stats-utils.js.map +1 -0
- package/dist/index.cjs +64 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +22 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/progress/manager.cjs +325 -0
- package/dist/progress/manager.cjs.map +1 -0
- package/dist/progress/manager.d.cts +125 -0
- package/dist/progress/manager.d.cts.map +1 -0
- package/dist/progress/manager.d.ts +125 -0
- package/dist/progress/manager.d.ts.map +1 -0
- package/dist/progress/manager.js +321 -0
- package/dist/progress/manager.js.map +1 -0
- package/dist/reporters/csv.cjs +250 -0
- package/dist/reporters/csv.cjs.map +1 -0
- package/dist/reporters/csv.d.cts +92 -0
- package/dist/reporters/csv.d.cts.map +1 -0
- package/dist/reporters/csv.d.ts +92 -0
- package/dist/reporters/csv.d.ts.map +1 -0
- package/dist/reporters/csv.js +246 -0
- package/dist/reporters/csv.js.map +1 -0
- package/dist/reporters/human.cjs +516 -0
- package/dist/reporters/human.cjs.map +1 -0
- package/dist/reporters/human.d.cts +86 -0
- package/dist/reporters/human.d.cts.map +1 -0
- package/dist/reporters/human.d.ts +86 -0
- package/dist/reporters/human.d.ts.map +1 -0
- package/dist/reporters/human.js +509 -0
- package/dist/reporters/human.js.map +1 -0
- package/dist/reporters/index.cjs +17 -0
- package/dist/reporters/index.cjs.map +1 -0
- package/dist/reporters/index.d.cts +10 -0
- package/dist/reporters/index.d.cts.map +1 -0
- package/dist/reporters/index.d.ts +10 -0
- package/dist/reporters/index.d.ts.map +1 -0
- package/dist/reporters/index.js +10 -0
- package/dist/reporters/index.js.map +1 -0
- package/dist/reporters/json.cjs +215 -0
- package/dist/reporters/json.cjs.map +1 -0
- package/dist/reporters/json.d.cts +79 -0
- package/dist/reporters/json.d.cts.map +1 -0
- package/dist/reporters/json.d.ts +79 -0
- package/dist/reporters/json.d.ts.map +1 -0
- package/dist/reporters/json.js +211 -0
- package/dist/reporters/json.js.map +1 -0
- package/dist/reporters/registry.cjs +255 -0
- package/dist/reporters/registry.cjs.map +1 -0
- package/dist/reporters/registry.d.cts +155 -0
- package/dist/reporters/registry.d.cts.map +1 -0
- package/dist/reporters/registry.d.ts +155 -0
- package/dist/reporters/registry.d.ts.map +1 -0
- package/dist/reporters/registry.js +249 -0
- package/dist/reporters/registry.js.map +1 -0
- package/dist/reporters/simple.cjs +328 -0
- package/dist/reporters/simple.cjs.map +1 -0
- package/dist/reporters/simple.d.cts +51 -0
- package/dist/reporters/simple.d.cts.map +1 -0
- package/dist/reporters/simple.d.ts +51 -0
- package/dist/reporters/simple.d.ts.map +1 -0
- package/dist/reporters/simple.js +321 -0
- package/dist/reporters/simple.js.map +1 -0
- package/dist/schema/modestbench-config.schema.json +162 -0
- package/dist/storage/history.cjs +456 -0
- package/dist/storage/history.cjs.map +1 -0
- package/dist/storage/history.d.cts +99 -0
- package/dist/storage/history.d.cts.map +1 -0
- package/dist/storage/history.d.ts +99 -0
- package/dist/storage/history.d.ts.map +1 -0
- package/dist/storage/history.js +452 -0
- package/dist/storage/history.js.map +1 -0
- package/dist/types/cli.cjs +21 -0
- package/dist/types/cli.cjs.map +1 -0
- package/dist/types/cli.d.cts +296 -0
- package/dist/types/cli.d.cts.map +1 -0
- package/dist/types/cli.d.ts +296 -0
- package/dist/types/cli.d.ts.map +1 -0
- package/dist/types/cli.js +18 -0
- package/dist/types/cli.js.map +1 -0
- package/dist/types/core.cjs +14 -0
- package/dist/types/core.cjs.map +1 -0
- package/dist/types/core.d.cts +380 -0
- package/dist/types/core.d.cts.map +1 -0
- package/dist/types/core.d.ts +380 -0
- package/dist/types/core.d.ts.map +1 -0
- package/dist/types/core.js +13 -0
- package/dist/types/core.js.map +1 -0
- package/dist/types/index.cjs +27 -0
- package/dist/types/index.cjs.map +1 -0
- package/dist/types/index.d.cts +11 -0
- package/dist/types/index.d.cts.map +1 -0
- package/dist/types/index.d.ts +11 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +11 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/interfaces.cjs +10 -0
- package/dist/types/interfaces.cjs.map +1 -0
- package/dist/types/interfaces.d.cts +381 -0
- package/dist/types/interfaces.d.cts.map +1 -0
- package/dist/types/interfaces.d.ts +381 -0
- package/dist/types/interfaces.d.ts.map +1 -0
- package/dist/types/interfaces.js +9 -0
- package/dist/types/interfaces.js.map +1 -0
- package/dist/types/utility.cjs +92 -0
- package/dist/types/utility.cjs.map +1 -0
- package/dist/types/utility.d.cts +330 -0
- package/dist/types/utility.d.cts.map +1 -0
- package/dist/types/utility.d.ts +330 -0
- package/dist/types/utility.d.ts.map +1 -0
- package/dist/types/utility.js +78 -0
- package/dist/types/utility.js.map +1 -0
- package/package.json +211 -0
- package/src/bootstrap.ts +35 -0
- package/src/cli/commands/history.ts +569 -0
- package/src/cli/commands/init.ts +658 -0
- package/src/cli/commands/run.ts +346 -0
- package/src/cli/index.ts +642 -0
- package/src/config/manager.ts +387 -0
- package/src/config/schema.ts +188 -0
- package/src/constants.ts +21 -0
- package/src/core/benchmark-schema.ts +185 -0
- package/src/core/engine.ts +888 -0
- package/src/core/engines/accurate-engine.ts +408 -0
- package/src/core/engines/index.ts +16 -0
- package/src/core/engines/tinybench-engine.ts +335 -0
- package/src/core/error-manager.ts +372 -0
- package/src/core/loader.ts +324 -0
- package/src/core/stats-utils.ts +135 -0
- package/src/index.ts +46 -0
- package/src/progress/manager.ts +415 -0
- package/src/reporters/csv.ts +368 -0
- package/src/reporters/human.ts +707 -0
- package/src/reporters/index.ts +10 -0
- package/src/reporters/json.ts +302 -0
- package/src/reporters/registry.ts +349 -0
- package/src/reporters/simple.ts +459 -0
- package/src/storage/history.ts +600 -0
- package/src/types/cli.ts +312 -0
- package/src/types/core.ts +414 -0
- package/src/types/index.ts +18 -0
- package/src/types/interfaces.ts +451 -0
- package/src/types/utility.ts +446 -0
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ModestBench File Loader
|
|
3
|
+
*
|
|
4
|
+
* Handles discovery, loading, and validation of benchmark files. Supports glob
|
|
5
|
+
* pattern matching and file structure validation.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { glob } from 'glob';
|
|
9
|
+
import { access, readFile, stat } from 'node:fs/promises';
|
|
10
|
+
import { extname } from 'node:path';
|
|
11
|
+
|
|
12
|
+
import type {
|
|
13
|
+
BenchmarkDefinition,
|
|
14
|
+
BenchmarkFile,
|
|
15
|
+
FileLoader,
|
|
16
|
+
ValidationError,
|
|
17
|
+
ValidationResult,
|
|
18
|
+
ValidationWarning,
|
|
19
|
+
} from '../types/index.js';
|
|
20
|
+
|
|
21
|
+
import {
|
|
22
|
+
BENCHMARK_FILE_EXTENSIONS,
|
|
23
|
+
BENCHMARK_FILE_PATTERN,
|
|
24
|
+
} from '../constants.js';
|
|
25
|
+
import { benchmarkFileSchema } from './benchmark-schema.js';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* File change notification for watch functionality
|
|
29
|
+
*/
|
|
30
|
+
interface FileChange {
|
|
31
|
+
readonly filePath: string;
|
|
32
|
+
readonly timestamp: Date;
|
|
33
|
+
readonly type: 'added' | 'deleted' | 'modified';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* File watcher interface
|
|
38
|
+
*/
|
|
39
|
+
interface FileWatcher {
|
|
40
|
+
close(): void;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Implementation of FileLoader for benchmark files
|
|
45
|
+
*/
|
|
46
|
+
export class BenchmarkFileLoader implements FileLoader {
|
|
47
|
+
private readonly supportedExtensions = BENCHMARK_FILE_EXTENSIONS;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Discover benchmark files using glob patterns or explicit file paths
|
|
51
|
+
*/
|
|
52
|
+
async discover(
|
|
53
|
+
pattern: string | string[],
|
|
54
|
+
exclude: string[] = [],
|
|
55
|
+
): Promise<string[]> {
|
|
56
|
+
try {
|
|
57
|
+
let patterns = Array.isArray(pattern) ? pattern : [pattern];
|
|
58
|
+
|
|
59
|
+
// Handle empty patterns - use sensible defaults
|
|
60
|
+
if (patterns.length === 0) {
|
|
61
|
+
patterns = [
|
|
62
|
+
`*${BENCHMARK_FILE_PATTERN}`, // top-level current directory
|
|
63
|
+
`bench/*${BENCHMARK_FILE_PATTERN}`, // top-level bench/ directory
|
|
64
|
+
];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Expand directory paths to recursive glob patterns
|
|
68
|
+
const expandedPatterns: string[] = [];
|
|
69
|
+
for (const p of patterns) {
|
|
70
|
+
try {
|
|
71
|
+
const stats = await stat(p);
|
|
72
|
+
if (stats.isDirectory()) {
|
|
73
|
+
// Directory: search recursively
|
|
74
|
+
expandedPatterns.push(`${p}/**/*${BENCHMARK_FILE_PATTERN}`);
|
|
75
|
+
} else {
|
|
76
|
+
// File or doesn't exist: use as-is (glob will handle it)
|
|
77
|
+
expandedPatterns.push(p);
|
|
78
|
+
}
|
|
79
|
+
} catch {
|
|
80
|
+
// Path doesn't exist, treat as glob pattern
|
|
81
|
+
expandedPatterns.push(p);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const allFiles = new Set<string>();
|
|
86
|
+
|
|
87
|
+
// Process each pattern
|
|
88
|
+
for (const p of expandedPatterns) {
|
|
89
|
+
const files = await glob(p, {
|
|
90
|
+
absolute: true,
|
|
91
|
+
ignore: exclude,
|
|
92
|
+
nodir: true,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Add discovered files to the set (automatic deduplication)
|
|
96
|
+
for (const file of files) {
|
|
97
|
+
allFiles.add(file);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Filter to supported file extensions
|
|
102
|
+
const supportedFiles = Array.from(allFiles).filter((file: string) => {
|
|
103
|
+
const ext = extname(file);
|
|
104
|
+
return this.supportedExtensions.has(ext);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
return supportedFiles.sort();
|
|
108
|
+
} catch (error) {
|
|
109
|
+
throw new Error(
|
|
110
|
+
`File discovery failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Load a single benchmark file
|
|
117
|
+
*/
|
|
118
|
+
async load(filePath: string): Promise<BenchmarkFile> {
|
|
119
|
+
try {
|
|
120
|
+
// Basic file checks (existence, extension)
|
|
121
|
+
const basicValidation = await this.validate(filePath);
|
|
122
|
+
if (!basicValidation.valid) {
|
|
123
|
+
throw new Error(
|
|
124
|
+
`Invalid benchmark file: ${basicValidation.errors.map((e) => e.message).join(', ')}`,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Read file content
|
|
129
|
+
const content = await readFile(filePath, 'utf-8');
|
|
130
|
+
|
|
131
|
+
// Get file stats for metadata
|
|
132
|
+
const stats = await stat(filePath);
|
|
133
|
+
|
|
134
|
+
// Load the module using dynamic import
|
|
135
|
+
const ext = extname(filePath);
|
|
136
|
+
let module: { [key: string]: unknown; default?: unknown };
|
|
137
|
+
|
|
138
|
+
if (ext === '.ts' || ext === '.mts' || ext === '.cts') {
|
|
139
|
+
// For TypeScript files, use cosmiconfig-typescript-loader
|
|
140
|
+
const { TypeScriptLoader: createTypeScriptLoader } = await import(
|
|
141
|
+
'cosmiconfig-typescript-loader'
|
|
142
|
+
);
|
|
143
|
+
const loader = createTypeScriptLoader();
|
|
144
|
+
module = (await loader(filePath, content)) as {
|
|
145
|
+
[key: string]: unknown;
|
|
146
|
+
default?: unknown;
|
|
147
|
+
};
|
|
148
|
+
} else {
|
|
149
|
+
// Use native dynamic import for JavaScript files with cache busting
|
|
150
|
+
// Add timestamp to prevent module caching issues across multiple loads
|
|
151
|
+
const timestamp = Date.now();
|
|
152
|
+
module = (await import(`${filePath}?t=${timestamp}`)) as {
|
|
153
|
+
[key: string]: unknown;
|
|
154
|
+
default?: unknown;
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const exports = module.default || module;
|
|
159
|
+
|
|
160
|
+
// Validate the loaded exports structure with Zod
|
|
161
|
+
const structureValidation = this.validateExports(filePath, exports);
|
|
162
|
+
if (!structureValidation.valid || !structureValidation.data) {
|
|
163
|
+
throw new Error(
|
|
164
|
+
`Invalid benchmark structure: ${structureValidation.errors.map((e) => e.message).join(', ')}`,
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Use the transformed/normalized data from Zod
|
|
169
|
+
// (this ensures shorthand functions are properly wrapped)
|
|
170
|
+
const normalizedExports = structureValidation.data;
|
|
171
|
+
|
|
172
|
+
// Analyze exports for metadata (simplified - structure already validated)
|
|
173
|
+
const hasDefaultExport = module.default !== undefined;
|
|
174
|
+
const exportNames = Object.keys(module);
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
content,
|
|
178
|
+
exports: normalizedExports,
|
|
179
|
+
filePath,
|
|
180
|
+
metadata: {
|
|
181
|
+
exportNames,
|
|
182
|
+
hasDefaultExport,
|
|
183
|
+
mtime: stats.mtime,
|
|
184
|
+
size: stats.size,
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
} catch (error) {
|
|
188
|
+
throw new Error(
|
|
189
|
+
`Failed to load file ${filePath}: ${error instanceof Error ? error.message : String(error)}`,
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Load multiple files in parallel
|
|
196
|
+
*/
|
|
197
|
+
async loadAll(filePaths: string[]): Promise<BenchmarkFile[]> {
|
|
198
|
+
try {
|
|
199
|
+
const loadPromises = filePaths.map((filePath) => this.load(filePath));
|
|
200
|
+
return await Promise.all(loadPromises);
|
|
201
|
+
} catch (error) {
|
|
202
|
+
throw new Error(
|
|
203
|
+
`Failed to load files: ${error instanceof Error ? error.message : String(error)}`,
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Validate benchmark file (basic checks only - file existence and extension)
|
|
210
|
+
* Structure validation happens after loading in the load() method
|
|
211
|
+
*/
|
|
212
|
+
async validate(filePath: string): Promise<ValidationResult> {
|
|
213
|
+
const errors: ValidationError[] = [];
|
|
214
|
+
const warnings: ValidationWarning[] = [];
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
// Check file existence
|
|
218
|
+
try {
|
|
219
|
+
await access(filePath);
|
|
220
|
+
} catch {
|
|
221
|
+
errors.push({
|
|
222
|
+
code: 'FILE_NOT_FOUND',
|
|
223
|
+
file: filePath,
|
|
224
|
+
message: 'File does not exist',
|
|
225
|
+
severity: 'error',
|
|
226
|
+
});
|
|
227
|
+
return { errors, files: [], valid: false, warnings };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Check file extension
|
|
231
|
+
const ext = extname(filePath);
|
|
232
|
+
if (!this.supportedExtensions.has(ext)) {
|
|
233
|
+
errors.push({
|
|
234
|
+
code: 'UNSUPPORTED_EXTENSION',
|
|
235
|
+
file: filePath,
|
|
236
|
+
message: `Unsupported file extension: ${ext}. Supported extensions: ${Array.from(this.supportedExtensions).join(', ')}`,
|
|
237
|
+
severity: 'error',
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
errors,
|
|
243
|
+
files: [filePath],
|
|
244
|
+
valid: errors.length === 0,
|
|
245
|
+
warnings,
|
|
246
|
+
};
|
|
247
|
+
} catch (error) {
|
|
248
|
+
errors.push({
|
|
249
|
+
code: 'VALIDATION_ERROR',
|
|
250
|
+
file: filePath,
|
|
251
|
+
message: `Validation failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
252
|
+
severity: 'error',
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
errors,
|
|
257
|
+
files: [filePath],
|
|
258
|
+
valid: false,
|
|
259
|
+
warnings,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Watch for file changes (placeholder implementation)
|
|
266
|
+
*/
|
|
267
|
+
watch(
|
|
268
|
+
_pattern: string,
|
|
269
|
+
_callback: (changes: FileChange[]) => void,
|
|
270
|
+
): FileWatcher {
|
|
271
|
+
// TODO: Implement file watching with chokidar or similar
|
|
272
|
+
// For now, return a no-op watcher
|
|
273
|
+
return {
|
|
274
|
+
close() {
|
|
275
|
+
// No-op
|
|
276
|
+
},
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Validate the structure of loaded exports using Zod schema Returns the
|
|
282
|
+
* transformed/normalized data if validation succeeds
|
|
283
|
+
*/
|
|
284
|
+
private validateExports(
|
|
285
|
+
filePath: string,
|
|
286
|
+
exports: unknown,
|
|
287
|
+
): {
|
|
288
|
+
data?: BenchmarkDefinition;
|
|
289
|
+
errors: ValidationError[];
|
|
290
|
+
valid: boolean;
|
|
291
|
+
warnings: ValidationWarning[];
|
|
292
|
+
} {
|
|
293
|
+
const errors: ValidationError[] = [];
|
|
294
|
+
const warnings: ValidationWarning[] = [];
|
|
295
|
+
|
|
296
|
+
try {
|
|
297
|
+
const result = benchmarkFileSchema.safeParse(exports);
|
|
298
|
+
|
|
299
|
+
if (!result.success) {
|
|
300
|
+
for (const issue of result.error.issues) {
|
|
301
|
+
const path = issue.path.length > 0 ? `${issue.path.join('.')}: ` : '';
|
|
302
|
+
errors.push({
|
|
303
|
+
code: 'INVALID_STRUCTURE',
|
|
304
|
+
file: filePath,
|
|
305
|
+
message: `${path}${issue.message}`,
|
|
306
|
+
severity: 'error',
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
return { errors, valid: false, warnings };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Return the transformed data (with shorthand functions normalized)
|
|
313
|
+
return { data: result.data, errors, valid: true, warnings };
|
|
314
|
+
} catch (error) {
|
|
315
|
+
errors.push({
|
|
316
|
+
code: 'VALIDATION_ERROR',
|
|
317
|
+
file: filePath,
|
|
318
|
+
message: `Structure validation failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
319
|
+
severity: 'error',
|
|
320
|
+
});
|
|
321
|
+
return { errors, valid: false, warnings };
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Statistical utility functions for benchmark result analysis
|
|
3
|
+
*
|
|
4
|
+
* Provides IQR-based outlier removal and comprehensive statistics calculation
|
|
5
|
+
* adapted from bench-node's StatisticalHistogram.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Statistics computed from sample data
|
|
10
|
+
*/
|
|
11
|
+
export interface SampleStatistics {
|
|
12
|
+
/** Coefficient of variation (stdDev/mean × 100) */
|
|
13
|
+
readonly cv: number;
|
|
14
|
+
/** Margin of error at 95% confidence */
|
|
15
|
+
readonly marginOfError: number;
|
|
16
|
+
/** Maximum value */
|
|
17
|
+
readonly max: number;
|
|
18
|
+
/** Mean (average) value */
|
|
19
|
+
readonly mean: number;
|
|
20
|
+
/** Minimum value */
|
|
21
|
+
readonly min: number;
|
|
22
|
+
/** 95th percentile */
|
|
23
|
+
readonly p95: number;
|
|
24
|
+
/** 99th percentile */
|
|
25
|
+
readonly p99: number;
|
|
26
|
+
/** Standard deviation */
|
|
27
|
+
readonly stdDev: number;
|
|
28
|
+
/** Variance */
|
|
29
|
+
readonly variance: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Calculate comprehensive statistics from samples
|
|
34
|
+
*
|
|
35
|
+
* Adapted from bench-node's StatisticalHistogram. Assumes samples are already
|
|
36
|
+
* sorted (e.g., from removeOutliersIQR output).
|
|
37
|
+
*
|
|
38
|
+
* @param samples - Sample values (should be sorted for accurate percentiles)
|
|
39
|
+
* @returns Computed statistics
|
|
40
|
+
*/
|
|
41
|
+
export const calculateStatistics = (samples: number[]): SampleStatistics => {
|
|
42
|
+
if (samples.length === 0) {
|
|
43
|
+
return {
|
|
44
|
+
cv: 0,
|
|
45
|
+
marginOfError: 0,
|
|
46
|
+
max: 0,
|
|
47
|
+
mean: 0,
|
|
48
|
+
min: 0,
|
|
49
|
+
p95: 0,
|
|
50
|
+
p99: 0,
|
|
51
|
+
stdDev: 0,
|
|
52
|
+
variance: 0,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Min/Max (samples are already sorted from removeOutliersIQR)
|
|
57
|
+
const min = samples[0]!;
|
|
58
|
+
const max = samples[samples.length - 1]!;
|
|
59
|
+
|
|
60
|
+
// Mean
|
|
61
|
+
const mean =
|
|
62
|
+
samples.length === 1
|
|
63
|
+
? samples[0]!
|
|
64
|
+
: samples.reduce(
|
|
65
|
+
(acc, v) => Math.min(Number.MAX_SAFE_INTEGER, acc + v),
|
|
66
|
+
0,
|
|
67
|
+
) / samples.length;
|
|
68
|
+
|
|
69
|
+
// Standard deviation and variance
|
|
70
|
+
let stdDev = 0;
|
|
71
|
+
let variance = 0;
|
|
72
|
+
if (samples.length >= 2) {
|
|
73
|
+
variance =
|
|
74
|
+
samples.reduce((acc, v) => acc + Math.pow(v - mean, 2), 0) /
|
|
75
|
+
(samples.length - 1);
|
|
76
|
+
stdDev = Math.sqrt(variance);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Coefficient of Variation
|
|
80
|
+
const cv = mean === 0 || samples.length < 2 ? 0 : (stdDev / mean) * 100;
|
|
81
|
+
|
|
82
|
+
// Margin of Error (95% confidence)
|
|
83
|
+
const Z = 1.96;
|
|
84
|
+
const marginOfError =
|
|
85
|
+
samples.length === 0 ? 0 : (Z * stdDev) / Math.sqrt(samples.length);
|
|
86
|
+
|
|
87
|
+
// Percentiles (using standard formula: floor((n-1) * p))
|
|
88
|
+
const p95 = samples[Math.floor((samples.length - 1) * 0.95)] ?? 0;
|
|
89
|
+
const p99 = samples[Math.floor((samples.length - 1) * 0.99)] ?? 0;
|
|
90
|
+
|
|
91
|
+
return { cv, marginOfError, max, mean, min, p95, p99, stdDev, variance };
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Remove outliers using Interquartile Range (IQR) method
|
|
96
|
+
*
|
|
97
|
+
* Adapted from bench-node's StatisticalHistogram.removeOutliers. Filters values
|
|
98
|
+
* outside [Q1 - 1.5×IQR, Q3 + 1.5×IQR] range.
|
|
99
|
+
*
|
|
100
|
+
* @param samples - Raw sample values (will be sorted internally)
|
|
101
|
+
* @returns Filtered samples with outliers removed
|
|
102
|
+
*/
|
|
103
|
+
export const removeOutliersIQR = (samples: number[]): number[] => {
|
|
104
|
+
if (samples.length < 4) {
|
|
105
|
+
return samples;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const sorted = samples.slice().sort((a, b) => a - b);
|
|
109
|
+
|
|
110
|
+
// Calculate Q1 and Q3
|
|
111
|
+
let q1: number;
|
|
112
|
+
let q3: number;
|
|
113
|
+
const size = sorted.length;
|
|
114
|
+
|
|
115
|
+
if (((size - 1) / 4) % 1 === 0 || (size / 4) % 1 === 0) {
|
|
116
|
+
q1 =
|
|
117
|
+
(1 / 2) *
|
|
118
|
+
(sorted[Math.floor(size / 4) - 1]! + sorted[Math.floor(size / 4)]!);
|
|
119
|
+
q3 =
|
|
120
|
+
(1 / 2) *
|
|
121
|
+
(sorted[Math.ceil((size * 3) / 4) - 1]! +
|
|
122
|
+
sorted[Math.ceil((size * 3) / 4)]!);
|
|
123
|
+
} else {
|
|
124
|
+
q1 = sorted[Math.floor(size / 4)]!;
|
|
125
|
+
q3 = sorted[Math.floor((size * 3) / 4)]!;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Calculate IQR and bounds
|
|
129
|
+
const iqr = q3 - q1;
|
|
130
|
+
const minValue = q1 - iqr * 1.5;
|
|
131
|
+
const maxValue = q3 + iqr * 1.5;
|
|
132
|
+
|
|
133
|
+
// Filter outliers
|
|
134
|
+
return sorted.filter((value) => value <= maxValue && value >= minValue);
|
|
135
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ModestBench Public API
|
|
3
|
+
*
|
|
4
|
+
* Main entry point for programmatic usage of ModestBench. This module exports
|
|
5
|
+
* all core classes, utilities, and types needed to use ModestBench as a
|
|
6
|
+
* library.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export { bootstrap as modestbench } from './bootstrap.js';
|
|
10
|
+
// Configuration management
|
|
11
|
+
export { ModestBenchConfigurationManager } from './config/manager.js';
|
|
12
|
+
|
|
13
|
+
// Core engine and loader
|
|
14
|
+
export { ModestBenchEngine } from './core/engine.js';
|
|
15
|
+
export { AccurateEngine, TinybenchEngine } from './core/engines/index.js';
|
|
16
|
+
|
|
17
|
+
// Error handling
|
|
18
|
+
export { ModestBenchErrorManager } from './core/error-manager.js';
|
|
19
|
+
|
|
20
|
+
export { BenchmarkFileLoader } from './core/loader.js';
|
|
21
|
+
|
|
22
|
+
// Statistical utilities
|
|
23
|
+
export {
|
|
24
|
+
calculateStatistics,
|
|
25
|
+
removeOutliersIQR,
|
|
26
|
+
type SampleStatistics,
|
|
27
|
+
} from './core/stats-utils.js';
|
|
28
|
+
|
|
29
|
+
// Progress tracking
|
|
30
|
+
export { ModestBenchProgressManager } from './progress/manager.js';
|
|
31
|
+
// Reporters
|
|
32
|
+
export { CsvReporter } from './reporters/csv.js';
|
|
33
|
+
export { HumanReporter } from './reporters/human.js';
|
|
34
|
+
export { JsonReporter } from './reporters/json.js';
|
|
35
|
+
|
|
36
|
+
export {
|
|
37
|
+
BaseReporter,
|
|
38
|
+
CompositeReporter,
|
|
39
|
+
ModestBenchReporterRegistry,
|
|
40
|
+
} from './reporters/registry.js';
|
|
41
|
+
|
|
42
|
+
// Storage
|
|
43
|
+
export { FileHistoryStorage } from './storage/history.js';
|
|
44
|
+
|
|
45
|
+
// Export all types
|
|
46
|
+
export * from './types/index.js';
|