modestbench 0.2.0 → 0.3.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 +20 -0
- package/README.md +131 -34
- package/dist/cli/commands/analyze.cjs +60 -0
- package/dist/cli/commands/analyze.cjs.map +1 -0
- package/dist/cli/commands/analyze.d.cts +35 -0
- package/dist/cli/commands/analyze.d.cts.map +1 -0
- package/dist/cli/commands/analyze.d.ts +35 -0
- package/dist/cli/commands/analyze.d.ts.map +1 -0
- package/dist/cli/commands/analyze.js +56 -0
- package/dist/cli/commands/analyze.js.map +1 -0
- package/dist/cli/commands/baseline.cjs +404 -0
- package/dist/cli/commands/baseline.cjs.map +1 -0
- package/dist/cli/commands/baseline.d.cts +72 -0
- package/dist/cli/commands/baseline.d.cts.map +1 -0
- package/dist/cli/commands/baseline.d.ts +72 -0
- package/dist/cli/commands/baseline.d.ts.map +1 -0
- package/dist/cli/commands/baseline.js +396 -0
- package/dist/cli/commands/baseline.js.map +1 -0
- package/dist/cli/commands/history.d.cts +1 -1
- package/dist/cli/commands/history.d.cts.map +1 -1
- package/dist/cli/commands/history.d.ts +1 -1
- package/dist/cli/commands/history.d.ts.map +1 -1
- package/dist/cli/commands/init.cjs +88 -155
- package/dist/cli/commands/init.cjs.map +1 -1
- package/dist/cli/commands/init.d.cts +4 -4
- package/dist/cli/commands/init.d.cts.map +1 -1
- package/dist/cli/commands/init.d.ts +4 -4
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +88 -155
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/run.cjs +132 -114
- package/dist/cli/commands/run.cjs.map +1 -1
- package/dist/cli/commands/run.d.cts +16 -3
- package/dist/cli/commands/run.d.cts.map +1 -1
- package/dist/cli/commands/run.d.ts +16 -3
- package/dist/cli/commands/run.d.ts.map +1 -1
- package/dist/cli/commands/run.js +131 -80
- package/dist/cli/commands/run.js.map +1 -1
- package/dist/cli/index.cjs +583 -394
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.d.cts +4 -16
- package/dist/cli/index.d.cts.map +1 -1
- package/dist/cli/index.d.ts +4 -16
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +575 -386
- package/dist/cli/index.js.map +1 -1
- package/dist/config/budget-schema.cjs +172 -0
- package/dist/config/budget-schema.cjs.map +1 -0
- package/dist/config/budget-schema.d.cts +59 -0
- package/dist/config/budget-schema.d.cts.map +1 -0
- package/dist/config/budget-schema.d.ts +59 -0
- package/dist/config/budget-schema.d.ts.map +1 -0
- package/dist/config/budget-schema.js +166 -0
- package/dist/config/budget-schema.js.map +1 -0
- package/dist/config/schema.cjs +182 -2
- package/dist/config/schema.cjs.map +1 -1
- package/dist/config/schema.d.cts +122 -3
- package/dist/config/schema.d.cts.map +1 -1
- package/dist/config/schema.d.ts +122 -3
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +180 -1
- package/dist/config/schema.js.map +1 -1
- package/dist/constants.cjs +45 -2
- package/dist/constants.cjs.map +1 -1
- package/dist/constants.d.cts +41 -0
- package/dist/constants.d.cts.map +1 -1
- package/dist/constants.d.ts +41 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +44 -1
- package/dist/constants.js.map +1 -1
- package/dist/core/engine.cjs +103 -21
- package/dist/core/engine.cjs.map +1 -1
- package/dist/core/engine.d.cts +7 -7
- package/dist/core/engine.d.cts.map +1 -1
- package/dist/core/engine.d.ts +7 -7
- package/dist/core/engine.d.ts.map +1 -1
- package/dist/core/engine.js +104 -22
- package/dist/core/engine.js.map +1 -1
- package/dist/core/output-path-resolver.cjs +8 -1
- package/dist/core/output-path-resolver.cjs.map +1 -1
- package/dist/core/output-path-resolver.d.cts.map +1 -1
- package/dist/core/output-path-resolver.d.ts.map +1 -1
- package/dist/core/output-path-resolver.js +9 -2
- package/dist/core/output-path-resolver.js.map +1 -1
- package/dist/errors/base.cjs +12 -3
- package/dist/errors/base.cjs.map +1 -1
- package/dist/errors/base.d.cts +7 -0
- package/dist/errors/base.d.cts.map +1 -1
- package/dist/errors/base.d.ts +7 -0
- package/dist/errors/base.d.ts.map +1 -1
- package/dist/errors/base.js +10 -2
- package/dist/errors/base.js.map +1 -1
- package/dist/errors/budget.cjs +37 -0
- package/dist/errors/budget.cjs.map +1 -0
- package/dist/errors/budget.d.cts +31 -0
- package/dist/errors/budget.d.cts.map +1 -0
- package/dist/errors/budget.d.ts +31 -0
- package/dist/errors/budget.d.ts.map +1 -0
- package/dist/errors/budget.js +33 -0
- package/dist/errors/budget.js.map +1 -0
- package/dist/errors/index.cjs +4 -1
- package/dist/errors/index.cjs.map +1 -1
- package/dist/errors/index.d.cts +1 -0
- package/dist/errors/index.d.cts.map +1 -1
- package/dist/errors/index.d.ts +1 -0
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/index.js +2 -0
- package/dist/errors/index.js.map +1 -1
- package/dist/index.cjs +13 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -0
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/reporters/csv.cjs +37 -17
- package/dist/reporters/csv.cjs.map +1 -1
- package/dist/reporters/csv.d.cts +3 -6
- package/dist/reporters/csv.d.cts.map +1 -1
- package/dist/reporters/csv.d.ts +3 -6
- package/dist/reporters/csv.d.ts.map +1 -1
- package/dist/reporters/csv.js +37 -17
- package/dist/reporters/csv.js.map +1 -1
- package/dist/reporters/human.cjs +66 -40
- package/dist/reporters/human.cjs.map +1 -1
- package/dist/reporters/human.d.cts +14 -13
- package/dist/reporters/human.d.cts.map +1 -1
- package/dist/reporters/human.d.ts +14 -13
- package/dist/reporters/human.d.ts.map +1 -1
- package/dist/reporters/human.js +66 -40
- package/dist/reporters/human.js.map +1 -1
- package/dist/reporters/json.cjs +23 -48
- package/dist/reporters/json.cjs.map +1 -1
- package/dist/reporters/json.d.cts +2 -28
- package/dist/reporters/json.d.cts.map +1 -1
- package/dist/reporters/json.d.ts +2 -28
- package/dist/reporters/json.d.ts.map +1 -1
- package/dist/reporters/json.js +25 -50
- package/dist/reporters/json.js.map +1 -1
- package/dist/reporters/profile-human.cjs +149 -0
- package/dist/reporters/profile-human.cjs.map +1 -0
- package/dist/reporters/profile-human.d.cts +44 -0
- package/dist/reporters/profile-human.d.cts.map +1 -0
- package/dist/reporters/profile-human.d.ts +44 -0
- package/dist/reporters/profile-human.d.ts.map +1 -0
- package/dist/reporters/profile-human.js +142 -0
- package/dist/reporters/profile-human.js.map +1 -0
- package/dist/reporters/simple.cjs +64 -44
- package/dist/reporters/simple.cjs.map +1 -1
- package/dist/reporters/simple.d.cts +14 -14
- package/dist/reporters/simple.d.cts.map +1 -1
- package/dist/reporters/simple.d.ts +14 -14
- package/dist/reporters/simple.d.ts.map +1 -1
- package/dist/reporters/simple.js +64 -44
- package/dist/reporters/simple.js.map +1 -1
- package/dist/schema/modestbench-config.schema.json +153 -0
- package/dist/services/baseline-storage.cjs +151 -0
- package/dist/services/baseline-storage.cjs.map +1 -0
- package/dist/services/baseline-storage.d.cts +55 -0
- package/dist/services/baseline-storage.d.cts.map +1 -0
- package/dist/services/baseline-storage.d.ts +55 -0
- package/dist/services/baseline-storage.d.ts.map +1 -0
- package/dist/services/baseline-storage.js +147 -0
- package/dist/services/baseline-storage.js.map +1 -0
- package/dist/services/budget-evaluator.cjs +146 -0
- package/dist/services/budget-evaluator.cjs.map +1 -0
- package/dist/services/budget-evaluator.d.cts +29 -0
- package/dist/services/budget-evaluator.d.cts.map +1 -0
- package/dist/services/budget-evaluator.d.ts +29 -0
- package/dist/services/budget-evaluator.d.ts.map +1 -0
- package/dist/services/budget-evaluator.js +142 -0
- package/dist/services/budget-evaluator.js.map +1 -0
- package/dist/services/config-manager.cjs +23 -9
- package/dist/services/config-manager.cjs.map +1 -1
- package/dist/services/config-manager.d.cts +6 -1
- package/dist/services/config-manager.d.cts.map +1 -1
- package/dist/services/config-manager.d.ts +6 -1
- package/dist/services/config-manager.d.ts.map +1 -1
- package/dist/services/config-manager.js +23 -9
- package/dist/services/config-manager.js.map +1 -1
- package/dist/services/file-loader.cjs +3 -6
- package/dist/services/file-loader.cjs.map +1 -1
- package/dist/services/file-loader.d.cts.map +1 -1
- package/dist/services/file-loader.d.ts.map +1 -1
- package/dist/services/file-loader.js +3 -6
- package/dist/services/file-loader.js.map +1 -1
- package/dist/services/profiler/profile-filter.cjs +113 -0
- package/dist/services/profiler/profile-filter.cjs.map +1 -0
- package/dist/services/profiler/profile-filter.d.cts +20 -0
- package/dist/services/profiler/profile-filter.d.cts.map +1 -0
- package/dist/services/profiler/profile-filter.d.ts +20 -0
- package/dist/services/profiler/profile-filter.d.ts.map +1 -0
- package/dist/services/profiler/profile-filter.js +109 -0
- package/dist/services/profiler/profile-filter.js.map +1 -0
- package/dist/services/profiler/profile-parser.cjs +139 -0
- package/dist/services/profiler/profile-parser.cjs.map +1 -0
- package/dist/services/profiler/profile-parser.d.cts +18 -0
- package/dist/services/profiler/profile-parser.d.cts.map +1 -0
- package/dist/services/profiler/profile-parser.d.ts +18 -0
- package/dist/services/profiler/profile-parser.d.ts.map +1 -0
- package/dist/services/profiler/profile-parser.js +132 -0
- package/dist/services/profiler/profile-parser.js.map +1 -0
- package/dist/services/profiler/profile-runner.cjs +90 -0
- package/dist/services/profiler/profile-runner.cjs.map +1 -0
- package/dist/services/profiler/profile-runner.d.cts +29 -0
- package/dist/services/profiler/profile-runner.d.cts.map +1 -0
- package/dist/services/profiler/profile-runner.d.ts +29 -0
- package/dist/services/profiler/profile-runner.d.ts.map +1 -0
- package/dist/services/profiler/profile-runner.js +86 -0
- package/dist/services/profiler/profile-runner.js.map +1 -0
- package/dist/services/reporter-registry.cjs +18 -24
- package/dist/services/reporter-registry.cjs.map +1 -1
- package/dist/services/reporter-registry.d.cts +18 -40
- package/dist/services/reporter-registry.d.cts.map +1 -1
- package/dist/services/reporter-registry.d.ts +18 -40
- package/dist/services/reporter-registry.d.ts.map +1 -1
- package/dist/services/reporter-registry.js +18 -24
- package/dist/services/reporter-registry.js.map +1 -1
- package/dist/types/budgets.cjs +8 -0
- package/dist/types/budgets.cjs.map +1 -0
- package/dist/types/budgets.d.cts +149 -0
- package/dist/types/budgets.d.cts.map +1 -0
- package/dist/types/budgets.d.ts +149 -0
- package/dist/types/budgets.d.ts.map +1 -0
- package/dist/types/budgets.js +7 -0
- package/dist/types/budgets.js.map +1 -0
- package/dist/types/cli.cjs +2 -11
- package/dist/types/cli.cjs.map +1 -1
- package/dist/types/cli.d.cts +3 -227
- package/dist/types/cli.d.cts.map +1 -1
- package/dist/types/cli.d.ts +3 -227
- package/dist/types/cli.d.ts.map +1 -1
- package/dist/types/cli.js +2 -11
- package/dist/types/cli.js.map +1 -1
- package/dist/types/core.cjs +6 -1
- package/dist/types/core.cjs.map +1 -1
- package/dist/types/core.d.cts +13 -2
- package/dist/types/core.d.cts.map +1 -1
- package/dist/types/core.d.ts +13 -2
- package/dist/types/core.d.ts.map +1 -1
- package/dist/types/core.js +2 -1
- package/dist/types/core.js.map +1 -1
- package/dist/types/index.cjs +5 -0
- package/dist/types/index.cjs.map +1 -1
- package/dist/types/index.d.cts +2 -0
- package/dist/types/index.d.cts.map +1 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/interfaces.d.cts +15 -8
- package/dist/types/interfaces.d.cts.map +1 -1
- package/dist/types/interfaces.d.ts +15 -8
- package/dist/types/interfaces.d.ts.map +1 -1
- package/dist/types/profiler.cjs +11 -0
- package/dist/types/profiler.cjs.map +1 -0
- package/dist/types/profiler.d.cts +100 -0
- package/dist/types/profiler.d.cts.map +1 -0
- package/dist/types/profiler.d.ts +100 -0
- package/dist/types/profiler.d.ts.map +1 -0
- package/dist/types/profiler.js +10 -0
- package/dist/types/profiler.js.map +1 -0
- package/dist/types/utility.cjs.map +1 -1
- package/dist/types/utility.d.cts +0 -8
- package/dist/types/utility.d.cts.map +1 -1
- package/dist/types/utility.d.ts +0 -8
- package/dist/types/utility.d.ts.map +1 -1
- package/dist/types/utility.js.map +1 -1
- package/dist/utils/identifiers.cjs +32 -0
- package/dist/utils/identifiers.cjs.map +1 -0
- package/dist/utils/identifiers.d.cts +32 -0
- package/dist/utils/identifiers.d.cts.map +1 -0
- package/dist/utils/identifiers.d.ts +32 -0
- package/dist/utils/identifiers.d.ts.map +1 -0
- package/dist/utils/identifiers.js +27 -0
- package/dist/utils/identifiers.js.map +1 -0
- package/dist/utils/package.cjs +40 -0
- package/dist/utils/package.cjs.map +1 -0
- package/dist/utils/package.d.cts +15 -0
- package/dist/utils/package.d.cts.map +1 -0
- package/dist/utils/package.d.ts +15 -0
- package/dist/utils/package.d.ts.map +1 -0
- package/dist/utils/package.js +33 -0
- package/dist/utils/package.js.map +1 -0
- package/dist/utils/type-guards.cjs +48 -0
- package/dist/utils/type-guards.cjs.map +1 -0
- package/dist/utils/type-guards.d.cts +22 -0
- package/dist/utils/type-guards.d.cts.map +1 -0
- package/dist/utils/type-guards.d.ts +22 -0
- package/dist/utils/type-guards.d.ts.map +1 -0
- package/dist/utils/type-guards.js +43 -0
- package/dist/utils/type-guards.js.map +1 -0
- package/package.json +10 -10
- package/src/cli/commands/analyze.ts +101 -0
- package/src/cli/commands/baseline.ts +577 -0
- package/src/cli/commands/history.ts +1 -1
- package/src/cli/commands/init.ts +105 -183
- package/src/cli/commands/run.ts +167 -98
- package/src/cli/index.ts +425 -183
- package/src/config/budget-schema.ts +189 -0
- package/src/config/schema.ts +260 -1
- package/src/constants.ts +53 -1
- package/src/core/engine.ts +151 -20
- package/src/core/output-path-resolver.ts +10 -2
- package/src/errors/base.ts +11 -2
- package/src/errors/budget.ts +38 -0
- package/src/errors/index.ts +3 -0
- package/src/index.ts +9 -0
- package/src/reporters/csv.ts +54 -25
- package/src/reporters/human.ts +88 -47
- package/src/reporters/json.ts +26 -71
- package/src/reporters/profile-human.ts +204 -0
- package/src/reporters/simple.ts +84 -53
- package/src/services/baseline-storage.ts +199 -0
- package/src/services/budget-evaluator.ts +182 -0
- package/src/services/config-manager.ts +23 -8
- package/src/services/file-loader.ts +3 -6
- package/src/services/profiler/profile-filter.ts +143 -0
- package/src/services/profiler/profile-parser.ts +194 -0
- package/src/services/profiler/profile-runner.ts +121 -0
- package/src/services/reporter-registry.ts +46 -81
- package/src/types/budgets.ts +180 -0
- package/src/types/cli.ts +5 -238
- package/src/types/core.ts +50 -10
- package/src/types/index.ts +5 -0
- package/src/types/interfaces.ts +16 -6
- package/src/types/profiler.ts +132 -0
- package/src/types/utility.ts +0 -10
- package/src/utils/identifiers.ts +58 -0
- package/src/utils/package.ts +35 -0
- package/src/utils/type-guards.ts +51 -0
package/src/reporters/simple.ts
CHANGED
|
@@ -9,8 +9,8 @@ import path from 'node:path';
|
|
|
9
9
|
|
|
10
10
|
import type {
|
|
11
11
|
BenchmarkRun,
|
|
12
|
+
BudgetSummary,
|
|
12
13
|
FileResult,
|
|
13
|
-
ProgressState,
|
|
14
14
|
SuiteResult,
|
|
15
15
|
TaskResult,
|
|
16
16
|
} from '../types/index.js';
|
|
@@ -62,6 +62,78 @@ export class SimpleReporter extends BaseReporter {
|
|
|
62
62
|
this.quiet = options.quiet ?? false;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Format bytes in human-readable format
|
|
67
|
+
*/
|
|
68
|
+
private static formatBytes(this: void, bytes: number): string {
|
|
69
|
+
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
70
|
+
let size = bytes;
|
|
71
|
+
let unitIndex = 0;
|
|
72
|
+
|
|
73
|
+
while (size >= 1024 && unitIndex < units.length - 1) {
|
|
74
|
+
size /= 1024;
|
|
75
|
+
unitIndex++;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return `${size.toFixed(1)} ${units[unitIndex]}`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Format file path - show relative path if within CWD, otherwise absolute
|
|
83
|
+
*/
|
|
84
|
+
private static formatPath(this: void, filePath: string): string {
|
|
85
|
+
const cwd = process.cwd();
|
|
86
|
+
const absolutePath = path.resolve(filePath);
|
|
87
|
+
|
|
88
|
+
// Check if the file is within the current working directory
|
|
89
|
+
if (absolutePath.startsWith(cwd + path.sep) || absolutePath === cwd) {
|
|
90
|
+
return path.relative(cwd, absolutePath);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return absolutePath;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Simple pluralization helper
|
|
98
|
+
*/
|
|
99
|
+
private static pluralize(this: void, str: string, count: number): string {
|
|
100
|
+
return count === 1 ? str : `${str}s`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
onBudgetResult(summary: BudgetSummary): void {
|
|
104
|
+
if (summary.total === 0 || this.quiet) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
console.log('== Performance Budgets');
|
|
109
|
+
console.log();
|
|
110
|
+
|
|
111
|
+
for (const result of summary.results) {
|
|
112
|
+
const icon = result.passed ? symbols.checkmark : symbols.cross;
|
|
113
|
+
console.log(` ${icon} ${result.taskId}`);
|
|
114
|
+
|
|
115
|
+
if (!result.passed && result.violations.length > 0) {
|
|
116
|
+
for (const violation of result.violations) {
|
|
117
|
+
console.log(` ${violation.message}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
console.log();
|
|
123
|
+
|
|
124
|
+
if (summary.failed === 0) {
|
|
125
|
+
console.log(
|
|
126
|
+
` ${symbols.checkmark} All ${summary.total} budget(s) passed`,
|
|
127
|
+
);
|
|
128
|
+
} else {
|
|
129
|
+
console.log(
|
|
130
|
+
` ${symbols.cross} ${summary.failed} of ${summary.total} budget(s) failed`,
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
console.log();
|
|
135
|
+
}
|
|
136
|
+
|
|
65
137
|
onEnd(run: BenchmarkRun): void {
|
|
66
138
|
if (this.quiet) {
|
|
67
139
|
return;
|
|
@@ -97,7 +169,7 @@ export class SimpleReporter extends BaseReporter {
|
|
|
97
169
|
console.log(`- Files: ${totalFiles}`);
|
|
98
170
|
console.log(`- Suites: ${totalSuites}`);
|
|
99
171
|
console.log(
|
|
100
|
-
`${symbols.approx} Duration: ${
|
|
172
|
+
`${symbols.approx} Duration: ${BaseReporter.formatDuration(duration * 1e6)}`,
|
|
101
173
|
);
|
|
102
174
|
console.log();
|
|
103
175
|
|
|
@@ -111,7 +183,7 @@ export class SimpleReporter extends BaseReporter {
|
|
|
111
183
|
console.log();
|
|
112
184
|
|
|
113
185
|
for (const failure of this.failures) {
|
|
114
|
-
const displayPath =
|
|
186
|
+
const displayPath = SimpleReporter.formatPath(failure.file);
|
|
115
187
|
console.log(` ${displayPath} > ${failure.suite} > ${failure.task}`);
|
|
116
188
|
console.log(` ${failure.error}`);
|
|
117
189
|
console.log();
|
|
@@ -155,7 +227,7 @@ export class SimpleReporter extends BaseReporter {
|
|
|
155
227
|
);
|
|
156
228
|
} else {
|
|
157
229
|
console.log(
|
|
158
|
-
` ${symbols.checkmark} ${totalPassed > 1 ? 'All ' : ''}${totalPassed} ${
|
|
230
|
+
` ${symbols.checkmark} ${totalPassed > 1 ? 'All ' : ''}${totalPassed} ${SimpleReporter.pluralize('task', totalPassed)} passed`,
|
|
159
231
|
);
|
|
160
232
|
}
|
|
161
233
|
|
|
@@ -169,15 +241,10 @@ export class SimpleReporter extends BaseReporter {
|
|
|
169
241
|
return;
|
|
170
242
|
}
|
|
171
243
|
|
|
172
|
-
const displayPath =
|
|
244
|
+
const displayPath = SimpleReporter.formatPath(file);
|
|
173
245
|
console.log(`-- ${displayPath}`);
|
|
174
246
|
}
|
|
175
247
|
|
|
176
|
-
onProgress(_state: ProgressState): void {
|
|
177
|
-
// Simple reporter does not display progress bars
|
|
178
|
-
return;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
248
|
onStart(run: BenchmarkRun): void {
|
|
182
249
|
this.startTime = Date.now();
|
|
183
250
|
this.failures = []; // Reset failures for new run
|
|
@@ -197,7 +264,9 @@ export class SimpleReporter extends BaseReporter {
|
|
|
197
264
|
console.log(
|
|
198
265
|
` cpu: ${run.environment.cpu.model} (${run.environment.cpu.cores} cores)`,
|
|
199
266
|
);
|
|
200
|
-
console.log(
|
|
267
|
+
console.log(
|
|
268
|
+
` mem: ${SimpleReporter.formatBytes(run.environment.memory.total)}`,
|
|
269
|
+
);
|
|
201
270
|
console.log();
|
|
202
271
|
}
|
|
203
272
|
|
|
@@ -231,7 +300,7 @@ export class SimpleReporter extends BaseReporter {
|
|
|
231
300
|
console.log(` ${symbols.cross} ${failed} failed, ${passed} passed`);
|
|
232
301
|
} else {
|
|
233
302
|
console.log(
|
|
234
|
-
` ${symbols.checkmark} ${passed} ${
|
|
303
|
+
` ${symbols.checkmark} ${passed} ${SimpleReporter.pluralize('task', passed)} passed`,
|
|
235
304
|
);
|
|
236
305
|
}
|
|
237
306
|
console.log();
|
|
@@ -275,44 +344,6 @@ export class SimpleReporter extends BaseReporter {
|
|
|
275
344
|
}
|
|
276
345
|
}
|
|
277
346
|
|
|
278
|
-
/**
|
|
279
|
-
* Format bytes in human-readable format
|
|
280
|
-
*/
|
|
281
|
-
private formatBytes(bytes: number): string {
|
|
282
|
-
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
283
|
-
let size = bytes;
|
|
284
|
-
let unitIndex = 0;
|
|
285
|
-
|
|
286
|
-
while (size >= 1024 && unitIndex < units.length - 1) {
|
|
287
|
-
size /= 1024;
|
|
288
|
-
unitIndex++;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
return `${size.toFixed(1)} ${units[unitIndex]}`;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
/**
|
|
295
|
-
* Format file path - show relative path if within CWD, otherwise absolute
|
|
296
|
-
*/
|
|
297
|
-
private formatPath(filePath: string): string {
|
|
298
|
-
const cwd = process.cwd();
|
|
299
|
-
const absolutePath = path.resolve(filePath);
|
|
300
|
-
|
|
301
|
-
// Check if the file is within the current working directory
|
|
302
|
-
if (absolutePath.startsWith(cwd + path.sep) || absolutePath === cwd) {
|
|
303
|
-
return path.relative(cwd, absolutePath);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
return absolutePath;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
/**
|
|
310
|
-
* Simple pluralization helper
|
|
311
|
-
*/
|
|
312
|
-
private pluralize(str: string, count: number): string {
|
|
313
|
-
return count === 1 ? str : `${str}s`;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
347
|
/**
|
|
317
348
|
* Print all task results in a suite with aligned columns
|
|
318
349
|
*/
|
|
@@ -364,9 +395,9 @@ export class SimpleReporter extends BaseReporter {
|
|
|
364
395
|
};
|
|
365
396
|
}
|
|
366
397
|
|
|
367
|
-
const duration =
|
|
368
|
-
const opsPerSec =
|
|
369
|
-
const rme =
|
|
398
|
+
const duration = BaseReporter.formatDuration(result.mean * 1e9);
|
|
399
|
+
const opsPerSec = BaseReporter.formatOpsPerSecond(result.opsPerSecond);
|
|
400
|
+
const rme = BaseReporter.formatPercentage(result.marginOfError * 100);
|
|
370
401
|
|
|
371
402
|
return {
|
|
372
403
|
durationLen: duration.length,
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
|
|
5
|
+
import type {
|
|
6
|
+
BaselineReference,
|
|
7
|
+
BaselineStorage,
|
|
8
|
+
BaselineSummaryData,
|
|
9
|
+
BenchmarkRun,
|
|
10
|
+
TaskId,
|
|
11
|
+
} from '../types/core.js';
|
|
12
|
+
|
|
13
|
+
import { validateBaselineStorage } from '../config/budget-schema.js';
|
|
14
|
+
import { StorageError } from '../errors/storage.js';
|
|
15
|
+
import { createTaskId } from '../types/core.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Service for managing named baselines
|
|
19
|
+
*
|
|
20
|
+
* @packageDocumentation
|
|
21
|
+
*/
|
|
22
|
+
export class BaselineStorageService {
|
|
23
|
+
private readonly storageDir: string;
|
|
24
|
+
|
|
25
|
+
private readonly storageFile: string;
|
|
26
|
+
|
|
27
|
+
constructor(storageDir: string = '.') {
|
|
28
|
+
this.storageDir = storageDir;
|
|
29
|
+
this.storageFile = join(storageDir, '.modestbench.baselines.json');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Delete a baseline
|
|
34
|
+
*/
|
|
35
|
+
async deleteBaseline(name: string): Promise<void> {
|
|
36
|
+
let storage = await this.loadStorage();
|
|
37
|
+
|
|
38
|
+
if (storage.baselines[name]) {
|
|
39
|
+
delete storage.baselines[name];
|
|
40
|
+
|
|
41
|
+
// Clear default if it was the deleted baseline
|
|
42
|
+
if (storage.default === name) {
|
|
43
|
+
storage = { ...storage, default: undefined };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
await this.saveStorage(storage);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Get a baseline by name
|
|
52
|
+
*/
|
|
53
|
+
async getBaseline(name: string): Promise<BaselineReference | null> {
|
|
54
|
+
const storage = await this.loadStorage();
|
|
55
|
+
return storage.baselines[name] ?? null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Get default baseline name
|
|
60
|
+
*/
|
|
61
|
+
async getDefault(): Promise<null | string> {
|
|
62
|
+
const storage = await this.loadStorage();
|
|
63
|
+
return storage.default ?? null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* List all baselines
|
|
68
|
+
*/
|
|
69
|
+
async listBaselines(): Promise<BaselineReference[]> {
|
|
70
|
+
const storage = await this.loadStorage();
|
|
71
|
+
return Object.values(storage.baselines).sort(
|
|
72
|
+
(a, b) => b.date.getTime() - a.date.getTime(),
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Resolve baseline name (use provided or fall back to default)
|
|
78
|
+
*/
|
|
79
|
+
async resolveBaselineName(name?: string): Promise<null | string> {
|
|
80
|
+
if (name) {
|
|
81
|
+
return name;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return await this.getDefault();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Save a benchmark run as a named baseline
|
|
89
|
+
*/
|
|
90
|
+
async saveBaseline(
|
|
91
|
+
name: string,
|
|
92
|
+
run: BenchmarkRun,
|
|
93
|
+
metadata?: {
|
|
94
|
+
branch?: string;
|
|
95
|
+
commit?: string;
|
|
96
|
+
},
|
|
97
|
+
): Promise<void> {
|
|
98
|
+
const storage = await this.loadStorage();
|
|
99
|
+
|
|
100
|
+
const baseline: BaselineReference = {
|
|
101
|
+
branch: metadata?.branch,
|
|
102
|
+
commit: metadata?.commit,
|
|
103
|
+
date:
|
|
104
|
+
run.startTime instanceof Date ? run.startTime : new Date(run.startTime),
|
|
105
|
+
name,
|
|
106
|
+
runId: run.id,
|
|
107
|
+
summary: this.extractSummary(run),
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
storage.baselines[name] = baseline;
|
|
111
|
+
|
|
112
|
+
await this.saveStorage(storage);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Set default baseline
|
|
117
|
+
*/
|
|
118
|
+
async setDefault(name: string): Promise<void> {
|
|
119
|
+
let storage = await this.loadStorage();
|
|
120
|
+
|
|
121
|
+
if (!storage.baselines[name]) {
|
|
122
|
+
throw new StorageError(
|
|
123
|
+
`Baseline "${name}" does not exist. Cannot set as default.`,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
storage = { ...storage, default: name };
|
|
128
|
+
await this.saveStorage(storage);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Extract task summary from benchmark run
|
|
133
|
+
*/
|
|
134
|
+
private extractSummary(
|
|
135
|
+
run: BenchmarkRun,
|
|
136
|
+
): Record<TaskId, BaselineSummaryData> {
|
|
137
|
+
const summary: Record<TaskId, BaselineSummaryData> = {};
|
|
138
|
+
|
|
139
|
+
for (const file of run.files) {
|
|
140
|
+
for (const suite of file.suites) {
|
|
141
|
+
for (const task of suite.tasks) {
|
|
142
|
+
if (!task.error) {
|
|
143
|
+
const taskId = createTaskId(file.filePath, suite.name, task.name);
|
|
144
|
+
summary[taskId] = {
|
|
145
|
+
mean: task.mean,
|
|
146
|
+
opsPerSecond: task.opsPerSecond,
|
|
147
|
+
p99: task.p99,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return summary;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Load baseline storage from disk
|
|
159
|
+
*/
|
|
160
|
+
private async loadStorage(): Promise<BaselineStorage> {
|
|
161
|
+
if (!existsSync(this.storageFile)) {
|
|
162
|
+
return {
|
|
163
|
+
baselines: {},
|
|
164
|
+
version: '1.0.0',
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
const content = await readFile(this.storageFile, 'utf-8');
|
|
170
|
+
const data = JSON.parse(content) as unknown;
|
|
171
|
+
return validateBaselineStorage(data);
|
|
172
|
+
} catch (error) {
|
|
173
|
+
throw new StorageError(
|
|
174
|
+
`Failed to load baseline storage from ${this.storageFile}`,
|
|
175
|
+
{ cause: error },
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Save baseline storage to disk
|
|
182
|
+
*/
|
|
183
|
+
private async saveStorage(storage: BaselineStorage): Promise<void> {
|
|
184
|
+
try {
|
|
185
|
+
// Ensure directory exists
|
|
186
|
+
if (!existsSync(this.storageDir)) {
|
|
187
|
+
await mkdir(this.storageDir, { recursive: true });
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const content = JSON.stringify(storage, null, 2);
|
|
191
|
+
await writeFile(this.storageFile, content, 'utf-8');
|
|
192
|
+
} catch (error) {
|
|
193
|
+
throw new StorageError(
|
|
194
|
+
`Failed to save baseline storage to ${this.storageFile}`,
|
|
195
|
+
{ cause: error },
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
BaselineSummaryData,
|
|
3
|
+
Budget,
|
|
4
|
+
BudgetResult,
|
|
5
|
+
BudgetSummary,
|
|
6
|
+
BudgetViolation,
|
|
7
|
+
TaskId,
|
|
8
|
+
TaskResult,
|
|
9
|
+
} from '../types/core.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Service for evaluating performance budgets
|
|
13
|
+
*
|
|
14
|
+
* @packageDocumentation
|
|
15
|
+
*/
|
|
16
|
+
export class BudgetEvaluator {
|
|
17
|
+
/**
|
|
18
|
+
* Format number with thousands separators
|
|
19
|
+
*/
|
|
20
|
+
private static formatNumber(this: void, value: number): string {
|
|
21
|
+
return value.toLocaleString('en-US', {
|
|
22
|
+
maximumFractionDigits: 0,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Format decimal as percentage
|
|
28
|
+
*/
|
|
29
|
+
private static formatPercentage(this: void, value: number): string {
|
|
30
|
+
return `${(value * 100).toFixed(1)}%`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Format time in nanoseconds to human-readable string
|
|
35
|
+
*/
|
|
36
|
+
private static formatTime(this: void, nanoseconds: number): string {
|
|
37
|
+
if (nanoseconds < 1_000) {
|
|
38
|
+
return `${nanoseconds.toFixed(0)}ns`;
|
|
39
|
+
} else if (nanoseconds < 1_000_000) {
|
|
40
|
+
return `${(nanoseconds / 1_000).toFixed(2)}μs`;
|
|
41
|
+
} else if (nanoseconds < 1_000_000_000) {
|
|
42
|
+
return `${(nanoseconds / 1_000_000).toFixed(2)}ms`;
|
|
43
|
+
} else {
|
|
44
|
+
return `${(nanoseconds / 1_000_000_000).toFixed(2)}s`;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Evaluate budgets for an entire benchmark run
|
|
50
|
+
*/
|
|
51
|
+
evaluateRun(
|
|
52
|
+
budgets: Record<string, Budget>,
|
|
53
|
+
taskResults: Map<TaskId, TaskResult>,
|
|
54
|
+
baselineData?: Map<TaskId, BaselineSummaryData>,
|
|
55
|
+
): BudgetSummary {
|
|
56
|
+
const results: BudgetResult[] = [];
|
|
57
|
+
|
|
58
|
+
for (const [taskId, budget] of Object.entries(budgets)) {
|
|
59
|
+
const taskResult = taskResults.get(taskId as TaskId);
|
|
60
|
+
|
|
61
|
+
// Skip if no result for this task
|
|
62
|
+
if (!taskResult) {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Skip relative budgets if no baseline data
|
|
67
|
+
if (budget.relative && !baselineData) {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const budgetResult = this.evaluateTask(
|
|
72
|
+
taskId as TaskId,
|
|
73
|
+
budget,
|
|
74
|
+
taskResult,
|
|
75
|
+
baselineData?.get(taskId as TaskId),
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
results.push(budgetResult);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const passed = results.filter((r) => r.passed).length;
|
|
82
|
+
const failed = results.filter((r) => !r.passed).length;
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
failed,
|
|
86
|
+
passed,
|
|
87
|
+
results,
|
|
88
|
+
total: results.length,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Evaluate budgets for a single task
|
|
94
|
+
*/
|
|
95
|
+
private evaluateTask(
|
|
96
|
+
taskId: TaskId,
|
|
97
|
+
budget: Budget,
|
|
98
|
+
actual: TaskResult,
|
|
99
|
+
baseline?: BaselineSummaryData,
|
|
100
|
+
): BudgetResult {
|
|
101
|
+
const violations: BudgetViolation[] = [];
|
|
102
|
+
|
|
103
|
+
// Evaluate absolute budgets
|
|
104
|
+
if (budget.absolute) {
|
|
105
|
+
if (budget.absolute.maxTime !== undefined) {
|
|
106
|
+
if (actual.mean > budget.absolute.maxTime) {
|
|
107
|
+
violations.push({
|
|
108
|
+
actual: actual.mean,
|
|
109
|
+
delta:
|
|
110
|
+
(actual.mean - budget.absolute.maxTime) / budget.absolute.maxTime,
|
|
111
|
+
message: `Mean execution time ${BudgetEvaluator.formatTime(actual.mean)} exceeded budget of ${BudgetEvaluator.formatTime(budget.absolute.maxTime)} by ${BudgetEvaluator.formatPercentage((actual.mean - budget.absolute.maxTime) / budget.absolute.maxTime)}`,
|
|
112
|
+
threshold: budget.absolute.maxTime,
|
|
113
|
+
type: 'maxTime',
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (budget.absolute.minOpsPerSec !== undefined) {
|
|
119
|
+
if (actual.opsPerSecond < budget.absolute.minOpsPerSec) {
|
|
120
|
+
violations.push({
|
|
121
|
+
actual: actual.opsPerSecond,
|
|
122
|
+
delta:
|
|
123
|
+
(budget.absolute.minOpsPerSec - actual.opsPerSecond) /
|
|
124
|
+
budget.absolute.minOpsPerSec,
|
|
125
|
+
message: `Operations per second ${BudgetEvaluator.formatNumber(actual.opsPerSecond)} is below minimum of ${BudgetEvaluator.formatNumber(budget.absolute.minOpsPerSec)} by ${BudgetEvaluator.formatPercentage((budget.absolute.minOpsPerSec - actual.opsPerSecond) / budget.absolute.minOpsPerSec)}`,
|
|
126
|
+
threshold: budget.absolute.minOpsPerSec,
|
|
127
|
+
type: 'minOpsPerSec',
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (budget.absolute.maxP99 !== undefined && actual.p99 !== undefined) {
|
|
133
|
+
if (actual.p99 > budget.absolute.maxP99) {
|
|
134
|
+
violations.push({
|
|
135
|
+
actual: actual.p99,
|
|
136
|
+
delta:
|
|
137
|
+
(actual.p99 - budget.absolute.maxP99) / budget.absolute.maxP99,
|
|
138
|
+
message: `P99 latency ${BudgetEvaluator.formatTime(actual.p99)} exceeded budget of ${BudgetEvaluator.formatTime(budget.absolute.maxP99)} by ${BudgetEvaluator.formatPercentage((actual.p99 - budget.absolute.maxP99) / budget.absolute.maxP99)}`,
|
|
139
|
+
threshold: budget.absolute.maxP99,
|
|
140
|
+
type: 'maxP99',
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Evaluate relative budgets
|
|
147
|
+
if (budget.relative && baseline) {
|
|
148
|
+
if (budget.relative.maxRegression !== undefined) {
|
|
149
|
+
const regression = (actual.mean - baseline.mean) / baseline.mean;
|
|
150
|
+
|
|
151
|
+
if (regression > budget.relative.maxRegression) {
|
|
152
|
+
violations.push({
|
|
153
|
+
actual: regression,
|
|
154
|
+
delta: regression - budget.relative.maxRegression,
|
|
155
|
+
message: `Performance regressed by ${BudgetEvaluator.formatPercentage(regression)} exceeding maximum allowed regression of ${BudgetEvaluator.formatPercentage(budget.relative.maxRegression)}`,
|
|
156
|
+
threshold: budget.relative.maxRegression,
|
|
157
|
+
type: 'maxRegression',
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
actual: {
|
|
165
|
+
mean: actual.mean,
|
|
166
|
+
opsPerSecond: actual.opsPerSecond,
|
|
167
|
+
p99: actual.p99,
|
|
168
|
+
},
|
|
169
|
+
baseline: baseline
|
|
170
|
+
? {
|
|
171
|
+
mean: baseline.mean,
|
|
172
|
+
opsPerSecond: baseline.opsPerSecond,
|
|
173
|
+
p99: baseline.p99,
|
|
174
|
+
}
|
|
175
|
+
: undefined,
|
|
176
|
+
budget,
|
|
177
|
+
passed: violations.length === 0,
|
|
178
|
+
taskId,
|
|
179
|
+
violations,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -56,7 +56,7 @@ const DEFAULT_CONFIG: ModestBenchConfig = {
|
|
|
56
56
|
limitBy: 'iterations', // Default to limiting by iteration count
|
|
57
57
|
metadata: {},
|
|
58
58
|
outputDir: './benchmark-results',
|
|
59
|
-
pattern: '
|
|
59
|
+
pattern: 'bench/**/*.bench.{js,ts,mjs,cjs,mts,cts}', // Search bench/ directory recursively
|
|
60
60
|
quiet: false,
|
|
61
61
|
reporterConfig: {},
|
|
62
62
|
reporters: [getDefaultReporter()],
|
|
@@ -121,10 +121,16 @@ export class ModestBenchConfigurationManager implements ConfigurationManager {
|
|
|
121
121
|
|
|
122
122
|
/**
|
|
123
123
|
* Load configuration from various sources with precedence
|
|
124
|
+
*
|
|
125
|
+
* @param configPath - Optional path to configuration file
|
|
126
|
+
* @param cliArgs - Optional CLI arguments to merge
|
|
127
|
+
* @param commandDefaults - Command-specific defaults (fallback to
|
|
128
|
+
* DEFAULT_CONFIG)
|
|
124
129
|
*/
|
|
125
130
|
async load(
|
|
126
131
|
configPath?: string,
|
|
127
132
|
cliArgs?: Record<string, unknown>,
|
|
133
|
+
commandDefaults?: Partial<ModestBenchConfig>,
|
|
128
134
|
): Promise<ModestBenchConfig> {
|
|
129
135
|
try {
|
|
130
136
|
// Create a fresh explorer for each load to avoid module caching issues
|
|
@@ -164,9 +170,13 @@ export class ModestBenchConfigurationManager implements ConfigurationManager {
|
|
|
164
170
|
|
|
165
171
|
const fileConfig = (result?.config || {}) as Partial<ModestBenchConfig>;
|
|
166
172
|
|
|
167
|
-
// 2. Merge: defaults <- file <- CLI args
|
|
173
|
+
// 2. Merge: command defaults <- file <- CLI args
|
|
174
|
+
// Use command-specific defaults if provided, otherwise use DEFAULT_CONFIG
|
|
175
|
+
const baseDefaults = commandDefaults
|
|
176
|
+
? this.merge(DEFAULT_CONFIG, commandDefaults)
|
|
177
|
+
: DEFAULT_CONFIG;
|
|
168
178
|
const normalizedCliArgs = cliArgs ? this.normalizeCliArgs(cliArgs) : {};
|
|
169
|
-
const merged = this.merge(
|
|
179
|
+
const merged = this.merge(baseDefaults, fileConfig, normalizedCliArgs);
|
|
170
180
|
|
|
171
181
|
// 2.5. Apply smart defaults for limitBy if not explicitly provided
|
|
172
182
|
const finalConfig = ModestBenchConfigurationManager.applySmartDefaults(
|
|
@@ -175,15 +185,20 @@ export class ModestBenchConfigurationManager implements ConfigurationManager {
|
|
|
175
185
|
fileConfig,
|
|
176
186
|
);
|
|
177
187
|
|
|
178
|
-
// 3. Validate final configuration
|
|
179
|
-
|
|
180
|
-
|
|
188
|
+
// 3. Validate final configuration and get transformed config
|
|
189
|
+
// The validation also transforms budgets from nested to flat format
|
|
190
|
+
const validation = safeParseConfig(finalConfig);
|
|
191
|
+
if (!validation.success) {
|
|
192
|
+
const errors = validation.error.issues.map((issue) => {
|
|
193
|
+
const path = issue.path.join('.');
|
|
194
|
+
return `${path ? `${path}: ` : ''}${issue.message}`;
|
|
195
|
+
});
|
|
181
196
|
throw new ConfigValidationError(
|
|
182
|
-
`Configuration validation failed: ${
|
|
197
|
+
`Configuration validation failed: ${errors.join(', ')}`,
|
|
183
198
|
);
|
|
184
199
|
}
|
|
185
200
|
|
|
186
|
-
return
|
|
201
|
+
return validation.data;
|
|
187
202
|
} catch (error) {
|
|
188
203
|
// Re-throw our custom errors
|
|
189
204
|
if (
|
|
@@ -64,8 +64,7 @@ export class BenchmarkFileLoader implements FileLoader {
|
|
|
64
64
|
// Handle empty patterns - use sensible defaults
|
|
65
65
|
if (patterns.length === 0) {
|
|
66
66
|
patterns = [
|
|
67
|
-
|
|
68
|
-
`bench/*${BENCHMARK_FILE_PATTERN}`, // top-level bench/ directory
|
|
67
|
+
`bench/**/*${BENCHMARK_FILE_PATTERN}`, // bench/ directory (recursive)
|
|
69
68
|
];
|
|
70
69
|
}
|
|
71
70
|
|
|
@@ -152,10 +151,8 @@ export class BenchmarkFileLoader implements FileLoader {
|
|
|
152
151
|
default?: unknown;
|
|
153
152
|
};
|
|
154
153
|
} else {
|
|
155
|
-
// Use native dynamic import for JavaScript files
|
|
156
|
-
|
|
157
|
-
const timestamp = Date.now();
|
|
158
|
-
module = (await import(`${filePath}?t=${timestamp}`)) as {
|
|
154
|
+
// Use native dynamic import for JavaScript files
|
|
155
|
+
module = (await import(filePath)) as {
|
|
159
156
|
[key: string]: unknown;
|
|
160
157
|
default?: unknown;
|
|
161
158
|
};
|