modestbench 0.1.0 → 0.2.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 +11 -0
- package/README.md +39 -31
- package/dist/bootstrap.cjs +10 -10
- package/dist/bootstrap.cjs.map +1 -1
- package/dist/bootstrap.d.cts.map +1 -1
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +5 -5
- package/dist/bootstrap.js.map +1 -1
- package/dist/cli/commands/history.cjs +108 -266
- package/dist/cli/commands/history.cjs.map +1 -1
- package/dist/cli/commands/history.d.cts +75 -12
- package/dist/cli/commands/history.d.cts.map +1 -1
- package/dist/cli/commands/history.d.ts +75 -12
- package/dist/cli/commands/history.d.ts.map +1 -1
- package/dist/cli/commands/history.js +105 -268
- package/dist/cli/commands/history.js.map +1 -1
- package/dist/cli/commands/run.cjs +18 -5
- 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 +18 -5
- package/dist/cli/commands/run.js.map +1 -1
- package/dist/cli/index.cjs +307 -91
- 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 +308 -92
- package/dist/cli/index.js.map +1 -1
- package/dist/core/engine.cjs +8 -1
- package/dist/core/engine.cjs.map +1 -1
- package/dist/core/engine.d.cts +3 -0
- package/dist/core/engine.d.cts.map +1 -1
- package/dist/core/engine.d.ts +3 -0
- package/dist/core/engine.d.ts.map +1 -1
- package/dist/core/engine.js +8 -1
- package/dist/core/engine.js.map +1 -1
- package/dist/core/output-path-resolver.cjs +34 -0
- package/dist/core/output-path-resolver.cjs.map +1 -0
- package/dist/core/output-path-resolver.d.cts +10 -0
- package/dist/core/output-path-resolver.d.cts.map +1 -0
- package/dist/core/output-path-resolver.d.ts +10 -0
- package/dist/core/output-path-resolver.d.ts.map +1 -0
- package/dist/core/output-path-resolver.js +30 -0
- package/dist/core/output-path-resolver.js.map +1 -0
- package/dist/formatters/history/base.cjs +9 -0
- package/dist/formatters/history/base.cjs.map +1 -0
- package/dist/formatters/history/base.d.cts +26 -0
- package/dist/formatters/history/base.d.cts.map +1 -0
- package/dist/formatters/history/base.d.ts +26 -0
- package/dist/formatters/history/base.d.ts.map +1 -0
- package/dist/formatters/history/base.js +8 -0
- package/dist/formatters/history/base.js.map +1 -0
- package/dist/formatters/history/compare.cjs +127 -0
- package/dist/formatters/history/compare.cjs.map +1 -0
- package/dist/formatters/history/compare.d.cts +21 -0
- package/dist/formatters/history/compare.d.cts.map +1 -0
- package/dist/formatters/history/compare.d.ts +21 -0
- package/dist/formatters/history/compare.d.ts.map +1 -0
- package/dist/formatters/history/compare.js +123 -0
- package/dist/formatters/history/compare.js.map +1 -0
- package/dist/formatters/history/list.cjs +74 -0
- package/dist/formatters/history/list.cjs.map +1 -0
- package/dist/formatters/history/list.d.cts +25 -0
- package/dist/formatters/history/list.d.cts.map +1 -0
- package/dist/formatters/history/list.d.ts +25 -0
- package/dist/formatters/history/list.d.ts.map +1 -0
- package/dist/formatters/history/list.js +70 -0
- package/dist/formatters/history/list.js.map +1 -0
- package/dist/formatters/history/show.cjs +98 -0
- package/dist/formatters/history/show.cjs.map +1 -0
- package/dist/formatters/history/show.d.cts +21 -0
- package/dist/formatters/history/show.d.cts.map +1 -0
- package/dist/formatters/history/show.d.ts +21 -0
- package/dist/formatters/history/show.d.ts.map +1 -0
- package/dist/formatters/history/show.js +94 -0
- package/dist/formatters/history/show.js.map +1 -0
- package/dist/formatters/history/trends.cjs +194 -0
- package/dist/formatters/history/trends.cjs.map +1 -0
- package/dist/formatters/history/trends.d.cts +22 -0
- package/dist/formatters/history/trends.d.cts.map +1 -0
- package/dist/formatters/history/trends.d.ts +22 -0
- package/dist/formatters/history/trends.d.ts.map +1 -0
- package/dist/formatters/history/trends.js +190 -0
- package/dist/formatters/history/trends.js.map +1 -0
- package/dist/formatters/history/visualization.cjs +79 -0
- package/dist/formatters/history/visualization.cjs.map +1 -0
- package/dist/formatters/history/visualization.d.cts +24 -0
- package/dist/formatters/history/visualization.d.cts.map +1 -0
- package/dist/formatters/history/visualization.d.ts +24 -0
- package/dist/formatters/history/visualization.d.ts.map +1 -0
- package/dist/formatters/history/visualization.js +74 -0
- package/dist/formatters/history/visualization.js.map +1 -0
- package/dist/index.cjs +15 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -5
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -9
- package/dist/index.js.map +1 -1
- package/dist/reporters/csv.cjs +2 -2
- package/dist/reporters/csv.cjs.map +1 -1
- package/dist/reporters/csv.d.cts +1 -1
- package/dist/reporters/csv.d.cts.map +1 -1
- package/dist/reporters/csv.d.ts +1 -1
- package/dist/reporters/csv.d.ts.map +1 -1
- package/dist/reporters/csv.js +1 -1
- package/dist/reporters/csv.js.map +1 -1
- package/dist/reporters/human.cjs +24 -62
- package/dist/reporters/human.cjs.map +1 -1
- package/dist/reporters/human.d.cts +1 -1
- package/dist/reporters/human.d.cts.map +1 -1
- package/dist/reporters/human.d.ts +1 -1
- package/dist/reporters/human.d.ts.map +1 -1
- package/dist/reporters/human.js +2 -40
- package/dist/reporters/human.js.map +1 -1
- package/dist/reporters/json.cjs +2 -2
- package/dist/reporters/json.cjs.map +1 -1
- package/dist/reporters/json.d.cts +1 -1
- package/dist/reporters/json.d.cts.map +1 -1
- package/dist/reporters/json.d.ts +1 -1
- package/dist/reporters/json.d.ts.map +1 -1
- package/dist/reporters/json.js +1 -1
- package/dist/reporters/json.js.map +1 -1
- package/dist/reporters/simple.cjs +2 -2
- package/dist/reporters/simple.cjs.map +1 -1
- package/dist/reporters/simple.d.cts +1 -1
- package/dist/reporters/simple.d.cts.map +1 -1
- package/dist/reporters/simple.d.ts +1 -1
- package/dist/reporters/simple.d.ts.map +1 -1
- package/dist/reporters/simple.js +1 -1
- package/dist/reporters/simple.js.map +1 -1
- package/dist/{config/manager.cjs → services/config-manager.cjs} +2 -2
- package/dist/services/config-manager.cjs.map +1 -0
- package/dist/{config/manager.d.cts → services/config-manager.d.cts} +1 -1
- package/dist/services/config-manager.d.cts.map +1 -0
- package/dist/{config/manager.d.ts → services/config-manager.d.ts} +1 -1
- package/dist/services/config-manager.d.ts.map +1 -0
- package/dist/{config/manager.js → services/config-manager.js} +2 -2
- package/dist/services/config-manager.js.map +1 -0
- package/dist/{core/loader.cjs → services/file-loader.cjs} +2 -2
- package/dist/services/file-loader.cjs.map +1 -0
- package/dist/{core/loader.d.cts → services/file-loader.d.cts} +1 -1
- package/dist/services/file-loader.d.cts.map +1 -0
- package/dist/{core/loader.d.ts → services/file-loader.d.ts} +1 -1
- package/dist/services/file-loader.d.ts.map +1 -0
- package/dist/{core/loader.js → services/file-loader.js} +2 -2
- package/dist/services/file-loader.js.map +1 -0
- package/dist/services/history/comparison.cjs +124 -0
- package/dist/services/history/comparison.cjs.map +1 -0
- package/dist/services/history/comparison.d.cts +18 -0
- package/dist/services/history/comparison.d.cts.map +1 -0
- package/dist/services/history/comparison.d.ts +18 -0
- package/dist/services/history/comparison.d.ts.map +1 -0
- package/dist/services/history/comparison.js +120 -0
- package/dist/services/history/comparison.js.map +1 -0
- package/dist/services/history/models.cjs +9 -0
- package/dist/services/history/models.cjs.map +1 -0
- package/dist/services/history/models.d.cts +139 -0
- package/dist/services/history/models.d.cts.map +1 -0
- package/dist/services/history/models.d.ts +139 -0
- package/dist/services/history/models.d.ts.map +1 -0
- package/dist/services/history/models.js +8 -0
- package/dist/services/history/models.js.map +1 -0
- package/dist/services/history/query.cjs +97 -0
- package/dist/services/history/query.cjs.map +1 -0
- package/dist/services/history/query.d.cts +38 -0
- package/dist/services/history/query.d.cts.map +1 -0
- package/dist/services/history/query.d.ts +38 -0
- package/dist/services/history/query.d.ts.map +1 -0
- package/dist/services/history/query.js +92 -0
- package/dist/services/history/query.js.map +1 -0
- package/dist/services/history/trend-analysis.cjs +187 -0
- package/dist/services/history/trend-analysis.cjs.map +1 -0
- package/dist/services/history/trend-analysis.d.cts +34 -0
- package/dist/services/history/trend-analysis.d.cts.map +1 -0
- package/dist/services/history/trend-analysis.d.ts +34 -0
- package/dist/services/history/trend-analysis.d.ts.map +1 -0
- package/dist/services/history/trend-analysis.js +179 -0
- package/dist/services/history/trend-analysis.js.map +1 -0
- package/dist/{storage/history.cjs → services/history-storage.cjs} +1 -1
- package/dist/services/history-storage.cjs.map +1 -0
- package/dist/{storage/history.d.cts → services/history-storage.d.cts} +1 -1
- package/dist/services/history-storage.d.cts.map +1 -0
- package/dist/{storage/history.d.ts → services/history-storage.d.ts} +1 -1
- package/dist/services/history-storage.d.ts.map +1 -0
- package/dist/{storage/history.js → services/history-storage.js} +1 -1
- package/dist/services/history-storage.js.map +1 -0
- package/dist/{progress/manager.cjs → services/progress-manager.cjs} +1 -1
- package/dist/services/progress-manager.cjs.map +1 -0
- package/dist/{progress/manager.d.cts → services/progress-manager.d.cts} +1 -1
- package/dist/services/progress-manager.d.cts.map +1 -0
- package/dist/{progress/manager.d.ts → services/progress-manager.d.ts} +1 -1
- package/dist/services/progress-manager.d.ts.map +1 -0
- package/dist/{progress/manager.js → services/progress-manager.js} +1 -1
- package/dist/services/progress-manager.js.map +1 -0
- package/dist/{reporters/registry.cjs → services/reporter-registry.cjs} +1 -1
- package/dist/services/reporter-registry.cjs.map +1 -0
- package/dist/{reporters/registry.d.cts → services/reporter-registry.d.cts} +1 -1
- package/dist/services/reporter-registry.d.cts.map +1 -0
- package/dist/{reporters/registry.d.ts → services/reporter-registry.d.ts} +1 -1
- package/dist/services/reporter-registry.d.ts.map +1 -0
- package/dist/{reporters/registry.js → services/reporter-registry.js} +1 -1
- package/dist/services/reporter-registry.js.map +1 -0
- package/dist/types/cli.d.cts +3 -0
- package/dist/types/cli.d.cts.map +1 -1
- package/dist/types/cli.d.ts +3 -0
- package/dist/types/cli.d.ts.map +1 -1
- package/dist/utils/ansi.cjs +61 -0
- package/dist/utils/ansi.cjs.map +1 -0
- package/dist/utils/ansi.d.cts +53 -0
- package/dist/utils/ansi.d.cts.map +1 -0
- package/dist/utils/ansi.d.ts +53 -0
- package/dist/utils/ansi.d.ts.map +1 -0
- package/dist/utils/ansi.js +57 -0
- package/dist/utils/ansi.js.map +1 -0
- package/package.json +5 -4
- package/src/bootstrap.ts +5 -5
- package/src/cli/commands/history.ts +194 -342
- package/src/cli/commands/run.ts +32 -3
- package/src/cli/index.ts +361 -106
- package/src/core/engine.ts +9 -1
- package/src/core/output-path-resolver.ts +38 -0
- package/src/formatters/history/base.ts +28 -0
- package/src/formatters/history/compare.ts +186 -0
- package/src/formatters/history/list.ts +101 -0
- package/src/formatters/history/show.ts +155 -0
- package/src/formatters/history/trends.ts +281 -0
- package/src/formatters/history/visualization.ts +93 -0
- package/src/index.ts +7 -11
- package/src/reporters/csv.ts +1 -1
- package/src/reporters/human.ts +2 -42
- package/src/reporters/json.ts +1 -1
- package/src/reporters/simple.ts +1 -1
- package/src/{config/manager.ts → services/config-manager.ts} +1 -1
- package/src/{core/loader.ts → services/file-loader.ts} +1 -1
- package/src/services/history/comparison.ts +130 -0
- package/src/services/history/models.ts +148 -0
- package/src/services/history/query.ts +116 -0
- package/src/services/history/trend-analysis.ts +238 -0
- package/src/types/cli.ts +3 -0
- package/src/utils/ansi.ts +59 -0
- package/dist/config/manager.cjs.map +0 -1
- package/dist/config/manager.d.cts.map +0 -1
- package/dist/config/manager.d.ts.map +0 -1
- package/dist/config/manager.js.map +0 -1
- package/dist/core/loader.cjs.map +0 -1
- package/dist/core/loader.d.cts.map +0 -1
- package/dist/core/loader.d.ts.map +0 -1
- package/dist/core/loader.js.map +0 -1
- package/dist/progress/manager.cjs.map +0 -1
- package/dist/progress/manager.d.cts.map +0 -1
- package/dist/progress/manager.d.ts.map +0 -1
- package/dist/progress/manager.js.map +0 -1
- package/dist/reporters/registry.cjs.map +0 -1
- package/dist/reporters/registry.d.cts.map +0 -1
- package/dist/reporters/registry.d.ts.map +0 -1
- package/dist/reporters/registry.js.map +0 -1
- package/dist/storage/history.cjs.map +0 -1
- package/dist/storage/history.d.cts.map +0 -1
- package/dist/storage/history.d.ts.map +0 -1
- package/dist/storage/history.js.map +0 -1
- /package/src/{storage/history.ts → services/history-storage.ts} +0 -0
- /package/src/{progress/manager.ts → services/progress-manager.ts} +0 -0
- /package/src/{reporters/registry.ts → services/reporter-registry.ts} +0 -0
package/src/cli/commands/run.ts
CHANGED
|
@@ -11,6 +11,7 @@ import type { BenchmarkRun } from '../../types/index.js';
|
|
|
11
11
|
import type { CliContext } from '../index.js';
|
|
12
12
|
|
|
13
13
|
import { ErrorCodes } from '../../constants.js';
|
|
14
|
+
import { resolveOutputPath } from '../../core/output-path-resolver.js';
|
|
14
15
|
import {
|
|
15
16
|
InvalidArgumentError,
|
|
16
17
|
type ModestBenchError,
|
|
@@ -32,6 +33,7 @@ interface RunOptions {
|
|
|
32
33
|
json?: boolean | undefined;
|
|
33
34
|
noColor?: boolean | undefined;
|
|
34
35
|
outputDir?: string | undefined;
|
|
36
|
+
outputFile?: string | undefined;
|
|
35
37
|
pattern: string[];
|
|
36
38
|
progress?: boolean | undefined;
|
|
37
39
|
quiet?: boolean | undefined;
|
|
@@ -61,6 +63,18 @@ export const handleRunCommand = async (
|
|
|
61
63
|
const showCliMessages = verbose && !options.quiet;
|
|
62
64
|
|
|
63
65
|
try {
|
|
66
|
+
// Validate --output-file usage
|
|
67
|
+
if (
|
|
68
|
+
options.outputFile &&
|
|
69
|
+
options.reporters &&
|
|
70
|
+
options.reporters.length > 1
|
|
71
|
+
) {
|
|
72
|
+
throw new InvalidArgumentError(
|
|
73
|
+
'--output-file can only be used with a single reporter. ' +
|
|
74
|
+
'Use --output <dir> for multiple reporters.',
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
64
78
|
// Step 1: Load and merge configuration
|
|
65
79
|
if (showCliMessages) {
|
|
66
80
|
console.error('Loading configuration...');
|
|
@@ -79,6 +93,7 @@ export const handleRunCommand = async (
|
|
|
79
93
|
showCliMessages,
|
|
80
94
|
options.quiet ?? false,
|
|
81
95
|
options.outputDir,
|
|
96
|
+
options.outputFile,
|
|
82
97
|
options.progress,
|
|
83
98
|
);
|
|
84
99
|
|
|
@@ -161,10 +176,13 @@ export const handleRunCommand = async (
|
|
|
161
176
|
|
|
162
177
|
return handleResults(executionResult, options, shouldBeQuiet);
|
|
163
178
|
} catch (error) {
|
|
164
|
-
// Re-throw
|
|
179
|
+
// Re-throw CLI errors so yargs fail handler can show help
|
|
165
180
|
if ((error as ModestBenchError).code === ErrorCodes.FILE_DISCOVERY_FAILED) {
|
|
166
181
|
throw error;
|
|
167
182
|
}
|
|
183
|
+
if ((error as ModestBenchError).code === ErrorCodes.CLI_INVALID_ARGUMENT) {
|
|
184
|
+
throw error;
|
|
185
|
+
}
|
|
168
186
|
|
|
169
187
|
if (!shouldBeQuiet) {
|
|
170
188
|
console.error(
|
|
@@ -298,6 +316,7 @@ const setupReporters = async (
|
|
|
298
316
|
showCliMessages: boolean,
|
|
299
317
|
explicitQuiet: boolean,
|
|
300
318
|
explicitOutputDir?: string,
|
|
319
|
+
explicitOutputFile?: string,
|
|
301
320
|
progressOption?: boolean,
|
|
302
321
|
) => {
|
|
303
322
|
try {
|
|
@@ -328,17 +347,27 @@ const setupReporters = async (
|
|
|
328
347
|
verbose: isVerbose,
|
|
329
348
|
});
|
|
330
349
|
} else if (reporterName === 'json') {
|
|
350
|
+
const outputPath = resolveOutputPath(
|
|
351
|
+
outputDir,
|
|
352
|
+
explicitOutputFile,
|
|
353
|
+
'results.json',
|
|
354
|
+
);
|
|
331
355
|
reporter = new JsonReporter({
|
|
332
|
-
...(
|
|
356
|
+
...(outputPath ? { outputPath } : {}),
|
|
333
357
|
prettyPrint: true,
|
|
334
358
|
quiet: shouldBeQuiet, // JSON uses shouldBeQuiet to avoid polluting stdout
|
|
335
359
|
verbose: isVerbose,
|
|
336
360
|
});
|
|
337
361
|
} else if (reporterName === 'csv') {
|
|
362
|
+
const outputPath = resolveOutputPath(
|
|
363
|
+
outputDir,
|
|
364
|
+
explicitOutputFile,
|
|
365
|
+
'results.csv',
|
|
366
|
+
);
|
|
338
367
|
reporter = new CsvReporter({
|
|
339
368
|
includeHeaders: true,
|
|
340
369
|
includeMetadata: true,
|
|
341
|
-
...(
|
|
370
|
+
...(outputPath ? { outputPath } : {}),
|
|
342
371
|
quiet: explicitQuiet, // Only applies explicit --quiet flag; CSV output can coexist with progress messages on different streams
|
|
343
372
|
verbose: isVerbose,
|
|
344
373
|
});
|
package/src/cli/index.ts
CHANGED
|
@@ -32,7 +32,14 @@ import {
|
|
|
32
32
|
SimpleReporter,
|
|
33
33
|
} from '../reporters/index.js';
|
|
34
34
|
// Import commands
|
|
35
|
-
import {
|
|
35
|
+
import {
|
|
36
|
+
handleCleanCommand,
|
|
37
|
+
handleCompareCommand,
|
|
38
|
+
handleExportCommand,
|
|
39
|
+
handleListCommand,
|
|
40
|
+
handleShowCommand,
|
|
41
|
+
handleTrendsCommand,
|
|
42
|
+
} from './commands/history.js';
|
|
36
43
|
import { handleInitCommand as initCommand } from './commands/init.js';
|
|
37
44
|
import { handleRunCommand as runCommand } from './commands/run.js';
|
|
38
45
|
|
|
@@ -104,6 +111,7 @@ export const main = async (
|
|
|
104
111
|
|
|
105
112
|
// Configure global options and commands
|
|
106
113
|
await cli
|
|
114
|
+
.scriptName('modestbench')
|
|
107
115
|
.option('config', {
|
|
108
116
|
alias: 'c',
|
|
109
117
|
description: 'Path to configuration file',
|
|
@@ -137,6 +145,7 @@ export const main = async (
|
|
|
137
145
|
})
|
|
138
146
|
.option('cwd', {
|
|
139
147
|
default: process.cwd(),
|
|
148
|
+
defaultDescription: '.',
|
|
140
149
|
description: 'Working directory',
|
|
141
150
|
global: true,
|
|
142
151
|
type: 'string',
|
|
@@ -187,6 +196,13 @@ export const main = async (
|
|
|
187
196
|
description: 'Output directory for reports',
|
|
188
197
|
type: 'string',
|
|
189
198
|
})
|
|
199
|
+
.option('output-file', {
|
|
200
|
+
alias: 'of',
|
|
201
|
+
description:
|
|
202
|
+
'Custom filename for reporter output (use with single reporter only)',
|
|
203
|
+
requiresArg: true,
|
|
204
|
+
type: 'string',
|
|
205
|
+
})
|
|
190
206
|
.option('iterations', {
|
|
191
207
|
alias: 'i',
|
|
192
208
|
description: 'Number of iterations per benchmark',
|
|
@@ -301,6 +317,7 @@ export const main = async (
|
|
|
301
317
|
json: argv.json,
|
|
302
318
|
noColor: argv.noColor,
|
|
303
319
|
outputDir: argv.output,
|
|
320
|
+
outputFile: argv['output-file'],
|
|
304
321
|
pattern: argv.pattern,
|
|
305
322
|
progress: argv.progress,
|
|
306
323
|
quiet: argv.quiet,
|
|
@@ -314,111 +331,349 @@ export const main = async (
|
|
|
314
331
|
process.exit(exitCode);
|
|
315
332
|
},
|
|
316
333
|
)
|
|
317
|
-
.command(
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
'
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
'
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
334
|
+
.command('history', 'View and manage benchmark history', (yargs) => {
|
|
335
|
+
return yargs
|
|
336
|
+
.command(
|
|
337
|
+
'list',
|
|
338
|
+
'List recent benchmark runs',
|
|
339
|
+
(yargs) => {
|
|
340
|
+
return yargs
|
|
341
|
+
.option('since', {
|
|
342
|
+
description:
|
|
343
|
+
'Show runs since date (ISO 8601 or relative like "1 week ago")',
|
|
344
|
+
type: 'string',
|
|
345
|
+
})
|
|
346
|
+
.option('until', {
|
|
347
|
+
description:
|
|
348
|
+
'Show runs until date (ISO 8601 or relative like "1 day ago")',
|
|
349
|
+
type: 'string',
|
|
350
|
+
})
|
|
351
|
+
.option('pattern', {
|
|
352
|
+
description: 'Filter by benchmark name pattern',
|
|
353
|
+
type: 'string',
|
|
354
|
+
})
|
|
355
|
+
.option('tags', {
|
|
356
|
+
description: 'Filter by tags (comma-separated)',
|
|
357
|
+
type: 'array',
|
|
358
|
+
})
|
|
359
|
+
.option('limit', {
|
|
360
|
+
default: 10,
|
|
361
|
+
description: 'Maximum number of results',
|
|
362
|
+
type: 'number',
|
|
363
|
+
})
|
|
364
|
+
.option('format', {
|
|
365
|
+
choices: ['human', 'json', 'csv'] as const,
|
|
366
|
+
default: 'human' as const,
|
|
367
|
+
description: 'Output format',
|
|
368
|
+
type: 'string',
|
|
369
|
+
})
|
|
370
|
+
.example([
|
|
371
|
+
['$0 history list', 'List recent benchmark runs'],
|
|
372
|
+
[
|
|
373
|
+
'$0 history list --since "1 week ago"',
|
|
374
|
+
'List runs from last week',
|
|
375
|
+
],
|
|
376
|
+
['$0 history list --limit 20', 'List 20 most recent runs'],
|
|
377
|
+
['$0 history list --format json', 'List runs in JSON format'],
|
|
378
|
+
]);
|
|
379
|
+
},
|
|
380
|
+
async (argv) => {
|
|
381
|
+
const context = await createCliContext(argv, abortController!);
|
|
382
|
+
const exitCode = await handleListCommand(context, {
|
|
383
|
+
cwd: argv.cwd,
|
|
384
|
+
format: argv.format,
|
|
385
|
+
limit: argv.limit,
|
|
386
|
+
pattern: argv.pattern,
|
|
387
|
+
quiet: Boolean(argv.quiet),
|
|
388
|
+
since: argv.since,
|
|
389
|
+
tags: argv.tags as string[] | undefined,
|
|
390
|
+
until: argv.until,
|
|
391
|
+
verbose: argv.verbose,
|
|
392
|
+
});
|
|
393
|
+
process.exit(exitCode);
|
|
394
|
+
},
|
|
395
|
+
)
|
|
396
|
+
.command(
|
|
397
|
+
'show <run-id>',
|
|
398
|
+
'Show detailed results for a specific run',
|
|
399
|
+
(yargs) => {
|
|
400
|
+
return yargs
|
|
401
|
+
.positional('run-id', {
|
|
402
|
+
describe: 'ID of the benchmark run to show',
|
|
403
|
+
type: 'string',
|
|
404
|
+
})
|
|
405
|
+
.option('format', {
|
|
406
|
+
choices: ['human', 'json', 'csv'] as const,
|
|
407
|
+
default: 'human' as const,
|
|
408
|
+
description: 'Output format',
|
|
409
|
+
type: 'string',
|
|
410
|
+
})
|
|
411
|
+
.example([
|
|
412
|
+
[
|
|
413
|
+
'$0 history show abc123',
|
|
414
|
+
'Show detailed results for run abc123',
|
|
415
|
+
],
|
|
416
|
+
[
|
|
417
|
+
'$0 history show abc123 --format json',
|
|
418
|
+
'Show run in JSON format',
|
|
419
|
+
],
|
|
420
|
+
]);
|
|
421
|
+
},
|
|
422
|
+
async (argv) => {
|
|
423
|
+
const context = await createCliContext(argv, abortController!);
|
|
424
|
+
const exitCode = await handleShowCommand(context, {
|
|
425
|
+
cwd: argv.cwd,
|
|
426
|
+
format: argv.format,
|
|
427
|
+
quiet: Boolean(argv.quiet),
|
|
428
|
+
runId: String(argv['run-id']),
|
|
429
|
+
verbose: argv.verbose,
|
|
430
|
+
});
|
|
431
|
+
process.exit(exitCode);
|
|
432
|
+
},
|
|
433
|
+
)
|
|
434
|
+
.command(
|
|
435
|
+
'compare <run-id1> <run-id2>',
|
|
436
|
+
'Compare two benchmark runs',
|
|
437
|
+
(yargs) => {
|
|
438
|
+
return yargs
|
|
439
|
+
.positional('run-id1', {
|
|
440
|
+
describe: 'ID of the first benchmark run',
|
|
441
|
+
type: 'string',
|
|
442
|
+
})
|
|
443
|
+
.positional('run-id2', {
|
|
444
|
+
describe: 'ID of the second benchmark run',
|
|
445
|
+
type: 'string',
|
|
446
|
+
})
|
|
447
|
+
.option('format', {
|
|
448
|
+
choices: ['human', 'json'] as const,
|
|
449
|
+
default: 'human' as const,
|
|
450
|
+
description: 'Output format',
|
|
451
|
+
type: 'string',
|
|
452
|
+
})
|
|
453
|
+
.example([
|
|
454
|
+
['$0 history compare abc123 def456', 'Compare two runs'],
|
|
455
|
+
[
|
|
456
|
+
'$0 history compare abc123 def456 --format json',
|
|
457
|
+
'Compare in JSON format',
|
|
458
|
+
],
|
|
459
|
+
]);
|
|
460
|
+
},
|
|
461
|
+
async (argv) => {
|
|
462
|
+
const context = await createCliContext(argv, abortController!);
|
|
463
|
+
const exitCode = await handleCompareCommand(context, {
|
|
464
|
+
cwd: argv.cwd,
|
|
465
|
+
format: argv.format,
|
|
466
|
+
quiet: Boolean(argv.quiet),
|
|
467
|
+
runId1: String(argv['run-id1']),
|
|
468
|
+
runId2: String(argv['run-id2']),
|
|
469
|
+
verbose: argv.verbose,
|
|
470
|
+
});
|
|
471
|
+
process.exit(exitCode);
|
|
472
|
+
},
|
|
473
|
+
)
|
|
474
|
+
.command(
|
|
475
|
+
'trends [pattern]',
|
|
476
|
+
'Show performance trends over time',
|
|
477
|
+
(yargs) => {
|
|
478
|
+
return yargs
|
|
479
|
+
.positional('pattern', {
|
|
480
|
+
describe: 'Filter by benchmark name pattern',
|
|
481
|
+
type: 'string',
|
|
482
|
+
})
|
|
483
|
+
.option('since', {
|
|
484
|
+
description:
|
|
485
|
+
'Show trends since date (ISO 8601 or relative like "1 week ago")',
|
|
486
|
+
type: 'string',
|
|
487
|
+
})
|
|
488
|
+
.option('until', {
|
|
489
|
+
description:
|
|
490
|
+
'Show trends until date (ISO 8601 or relative like "1 day ago")',
|
|
491
|
+
type: 'string',
|
|
492
|
+
})
|
|
493
|
+
.option('tags', {
|
|
494
|
+
description: 'Filter by tags (comma-separated)',
|
|
495
|
+
type: 'array',
|
|
496
|
+
})
|
|
497
|
+
.option('limit', {
|
|
498
|
+
description: 'Maximum number of runs to analyze',
|
|
499
|
+
type: 'number',
|
|
500
|
+
})
|
|
501
|
+
.option('all', {
|
|
502
|
+
alias: 'a',
|
|
503
|
+
default: false,
|
|
504
|
+
description: 'Analyze all runs (ignore limit)',
|
|
505
|
+
type: 'boolean',
|
|
506
|
+
})
|
|
507
|
+
.option('format', {
|
|
508
|
+
choices: ['human', 'json'] as const,
|
|
509
|
+
default: 'human' as const,
|
|
510
|
+
description: 'Output format',
|
|
511
|
+
type: 'string',
|
|
512
|
+
})
|
|
513
|
+
.example([
|
|
514
|
+
[
|
|
515
|
+
'$0 history trends',
|
|
516
|
+
'Show performance trends for all benchmarks',
|
|
517
|
+
],
|
|
518
|
+
[
|
|
519
|
+
'$0 history trends --since "1 month ago"',
|
|
520
|
+
'Show trends from last month',
|
|
521
|
+
],
|
|
522
|
+
[
|
|
523
|
+
'$0 history trends "array-*"',
|
|
524
|
+
'Show trends for array benchmarks',
|
|
525
|
+
],
|
|
526
|
+
[
|
|
527
|
+
'$0 history trends --format json',
|
|
528
|
+
'Output trends in JSON format',
|
|
529
|
+
],
|
|
530
|
+
]);
|
|
531
|
+
},
|
|
532
|
+
async (argv) => {
|
|
533
|
+
const context = await createCliContext(argv, abortController!);
|
|
534
|
+
const exitCode = await handleTrendsCommand(context, {
|
|
535
|
+
all: Boolean(argv.all),
|
|
536
|
+
cwd: argv.cwd,
|
|
537
|
+
format: argv.format,
|
|
538
|
+
limit: argv.limit,
|
|
539
|
+
pattern: argv.pattern,
|
|
540
|
+
quiet: Boolean(argv.quiet),
|
|
541
|
+
since: argv.since,
|
|
542
|
+
tags: argv.tags as string[] | undefined,
|
|
543
|
+
until: argv.until,
|
|
544
|
+
verbose: argv.verbose,
|
|
545
|
+
});
|
|
546
|
+
process.exit(exitCode);
|
|
547
|
+
},
|
|
548
|
+
)
|
|
549
|
+
.command(
|
|
550
|
+
'clean',
|
|
551
|
+
'Clean up old benchmark history',
|
|
552
|
+
(yargs) => {
|
|
553
|
+
return yargs
|
|
554
|
+
.option('max-age', {
|
|
555
|
+
description: 'Remove runs older than this many days',
|
|
556
|
+
type: 'number',
|
|
557
|
+
})
|
|
558
|
+
.option('max-runs', {
|
|
559
|
+
description: 'Keep only this many most recent runs',
|
|
560
|
+
type: 'number',
|
|
561
|
+
})
|
|
562
|
+
.option('max-size', {
|
|
563
|
+
description: 'Keep history under this size in bytes',
|
|
564
|
+
type: 'number',
|
|
565
|
+
})
|
|
566
|
+
.option('confirm', {
|
|
567
|
+
default: false,
|
|
568
|
+
description: 'Confirm cleanup without prompting',
|
|
569
|
+
type: 'boolean',
|
|
570
|
+
})
|
|
571
|
+
.check((argv) => {
|
|
572
|
+
if (
|
|
573
|
+
!argv['max-age'] &&
|
|
574
|
+
!argv['max-runs'] &&
|
|
575
|
+
!argv['max-size']
|
|
576
|
+
) {
|
|
577
|
+
throw new Error(
|
|
578
|
+
'At least one cleanup criterion must be specified (--max-age, --max-runs, or --max-size)',
|
|
579
|
+
);
|
|
580
|
+
}
|
|
581
|
+
return true;
|
|
582
|
+
})
|
|
583
|
+
.example([
|
|
584
|
+
[
|
|
585
|
+
'$0 history clean --max-runs 50 --confirm',
|
|
586
|
+
'Keep only latest 50 runs',
|
|
587
|
+
],
|
|
588
|
+
[
|
|
589
|
+
'$0 history clean --max-age 30',
|
|
590
|
+
'Preview removing runs older than 30 days',
|
|
591
|
+
],
|
|
592
|
+
[
|
|
593
|
+
'$0 history clean --max-size 10485760',
|
|
594
|
+
'Keep history under 10MB',
|
|
595
|
+
],
|
|
596
|
+
]);
|
|
597
|
+
},
|
|
598
|
+
async (argv) => {
|
|
599
|
+
const context = await createCliContext(argv, abortController!);
|
|
600
|
+
const exitCode = await handleCleanCommand(context, {
|
|
601
|
+
confirm: argv.confirm,
|
|
602
|
+
cwd: argv.cwd,
|
|
603
|
+
maxAge: argv['max-age'],
|
|
604
|
+
maxRuns: argv['max-runs'],
|
|
605
|
+
maxSize: argv['max-size'],
|
|
606
|
+
quiet: Boolean(argv.quiet),
|
|
607
|
+
verbose: argv.verbose,
|
|
608
|
+
});
|
|
609
|
+
process.exit(exitCode);
|
|
610
|
+
},
|
|
611
|
+
)
|
|
612
|
+
.command(
|
|
613
|
+
'export',
|
|
614
|
+
'Export benchmark history to a file',
|
|
615
|
+
(yargs) => {
|
|
616
|
+
return yargs
|
|
617
|
+
.option('format', {
|
|
618
|
+
choices: ['json', 'csv'] as const,
|
|
619
|
+
default: 'json' as const,
|
|
620
|
+
description: 'Export format',
|
|
621
|
+
type: 'string',
|
|
622
|
+
})
|
|
623
|
+
.option('output', {
|
|
624
|
+
alias: 'o',
|
|
625
|
+
demandOption: true,
|
|
626
|
+
description: 'Output file path',
|
|
627
|
+
type: 'string',
|
|
628
|
+
})
|
|
629
|
+
.option('since', {
|
|
630
|
+
description: 'Export runs since date',
|
|
631
|
+
type: 'string',
|
|
632
|
+
})
|
|
633
|
+
.option('until', {
|
|
634
|
+
description: 'Export runs until date',
|
|
635
|
+
type: 'string',
|
|
636
|
+
})
|
|
637
|
+
.example([
|
|
638
|
+
[
|
|
639
|
+
'$0 history export -o history.json',
|
|
640
|
+
'Export all history to JSON',
|
|
641
|
+
],
|
|
642
|
+
[
|
|
643
|
+
'$0 history export -o history.csv --format csv',
|
|
644
|
+
'Export to CSV',
|
|
645
|
+
],
|
|
646
|
+
[
|
|
647
|
+
'$0 history export -o recent.json --since "1 week ago"',
|
|
648
|
+
'Export recent runs',
|
|
649
|
+
],
|
|
650
|
+
]);
|
|
651
|
+
},
|
|
652
|
+
async (argv) => {
|
|
653
|
+
const context = await createCliContext(argv, abortController!);
|
|
654
|
+
const exitCode = await handleExportCommand(context, {
|
|
655
|
+
cwd: argv.cwd,
|
|
656
|
+
format: argv.format,
|
|
657
|
+
outputPath: argv.output,
|
|
658
|
+
quiet: Boolean(argv.quiet),
|
|
659
|
+
since: argv.since,
|
|
660
|
+
until: argv.until,
|
|
661
|
+
verbose: argv.verbose,
|
|
662
|
+
});
|
|
663
|
+
process.exit(exitCode);
|
|
664
|
+
},
|
|
665
|
+
)
|
|
666
|
+
.demandCommand(1, 'You must specify a history subcommand')
|
|
667
|
+
.strict()
|
|
668
|
+
.example([
|
|
669
|
+
['$0 history list', 'List recent benchmark runs'],
|
|
670
|
+
['$0 history show <run-id>', 'Show detailed results'],
|
|
671
|
+
['$0 history compare <run-id1> <run-id2>', 'Compare two runs'],
|
|
672
|
+
['$0 history trends', 'Show performance trends'],
|
|
673
|
+
['$0 history clean --max-runs 50', 'Keep only latest 50 runs'],
|
|
674
|
+
['$0 history export -o data.json', 'Export history'],
|
|
675
|
+
]);
|
|
676
|
+
})
|
|
422
677
|
.command(
|
|
423
678
|
'init [type]',
|
|
424
679
|
'Initialize a new benchmark project',
|
package/src/core/engine.ts
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
* architecture.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import { randomBytes } from 'node:crypto';
|
|
10
|
+
|
|
9
11
|
import type {
|
|
10
12
|
BenchmarkDefinition,
|
|
11
13
|
BenchmarkEngine,
|
|
@@ -697,9 +699,15 @@ export abstract class ModestBenchEngine implements BenchmarkEngine {
|
|
|
697
699
|
|
|
698
700
|
/**
|
|
699
701
|
* Generate a unique run ID
|
|
702
|
+
*
|
|
703
|
+
* Uses crypto.randomBytes for cryptographically random 7-character IDs.
|
|
704
|
+
* Format: 7 lowercase alphanumeric characters (e.g., "k3m9x2p")
|
|
700
705
|
*/
|
|
701
706
|
private generateRunId(): string {
|
|
702
|
-
|
|
707
|
+
// Generate random bytes, convert to hex, then to base36, take first 7 chars
|
|
708
|
+
const hex = randomBytes(4).toString('hex');
|
|
709
|
+
const num = parseInt(hex, 16);
|
|
710
|
+
return num.toString(36).padStart(7, '0').substring(0, 7);
|
|
703
711
|
}
|
|
704
712
|
|
|
705
713
|
/**
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { isAbsolute, join, resolve } from 'node:path';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Resolves the final output path for a reporter
|
|
5
|
+
*
|
|
6
|
+
* @param outputDir - Optional output directory from --output flag
|
|
7
|
+
* @param outputFile - Optional output filename from --output-file flag
|
|
8
|
+
* @param defaultFilename - Default filename to use if none specified
|
|
9
|
+
* @returns Resolved output path, or undefined if no output to file requested
|
|
10
|
+
*/
|
|
11
|
+
export const resolveOutputPath = (
|
|
12
|
+
outputDir?: string,
|
|
13
|
+
outputFile?: string,
|
|
14
|
+
defaultFilename?: string,
|
|
15
|
+
): string | undefined => {
|
|
16
|
+
// If outputFile is provided
|
|
17
|
+
if (outputFile) {
|
|
18
|
+
// If outputFile is absolute, use as-is
|
|
19
|
+
if (isAbsolute(outputFile)) {
|
|
20
|
+
return outputFile;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// If outputDir specified, join them
|
|
24
|
+
if (outputDir) {
|
|
25
|
+
return join(outputDir, outputFile);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Otherwise, resolve relative to cwd
|
|
29
|
+
return resolve(process.cwd(), outputFile);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Fall back to default behavior
|
|
33
|
+
if (outputDir && defaultFilename) {
|
|
34
|
+
return join(outputDir, defaultFilename);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return undefined;
|
|
38
|
+
};
|