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,569 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ModestBench History Command
|
|
3
|
+
*
|
|
4
|
+
* View and manage benchmark run history with subcommands for listing, showing,
|
|
5
|
+
* comparing, and cleaning historical data.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { HistoryQuery, RetentionPolicy } from '../../types/index.js';
|
|
9
|
+
import type { CliContext } from '../index.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* History command options interface
|
|
13
|
+
*/
|
|
14
|
+
interface HistoryOptions {
|
|
15
|
+
args?: string[] | undefined; // Additional arguments after subcommand
|
|
16
|
+
confirm?: boolean | undefined;
|
|
17
|
+
cwd: string;
|
|
18
|
+
format?: 'csv' | 'human' | 'json' | undefined;
|
|
19
|
+
limit?: number | undefined;
|
|
20
|
+
maxAge?: number | undefined;
|
|
21
|
+
maxRuns?: number | undefined;
|
|
22
|
+
maxSize?: number | undefined;
|
|
23
|
+
outputDir?: string | undefined;
|
|
24
|
+
pattern?: string | undefined;
|
|
25
|
+
quiet?: boolean | undefined;
|
|
26
|
+
since?: string | undefined;
|
|
27
|
+
subcommand: 'clean' | 'compare' | 'export' | 'list' | 'show' | 'trends';
|
|
28
|
+
tags?: string[] | undefined;
|
|
29
|
+
until?: string | undefined;
|
|
30
|
+
verbose?: boolean | undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Handle history command
|
|
35
|
+
*/
|
|
36
|
+
export const handleHistoryCommand = async (
|
|
37
|
+
context: CliContext,
|
|
38
|
+
options: HistoryOptions,
|
|
39
|
+
): Promise<number> => {
|
|
40
|
+
try {
|
|
41
|
+
// Get the subcommand
|
|
42
|
+
const subcommand = options.subcommand;
|
|
43
|
+
|
|
44
|
+
switch (subcommand) {
|
|
45
|
+
case 'clean':
|
|
46
|
+
return await handleCleanCommand(context, options);
|
|
47
|
+
case 'compare':
|
|
48
|
+
return await handleCompareCommand(context, options);
|
|
49
|
+
case 'export':
|
|
50
|
+
return await handleExportCommand(context, options);
|
|
51
|
+
case 'list':
|
|
52
|
+
return await handleListCommand(context, options);
|
|
53
|
+
case 'show':
|
|
54
|
+
return await handleShowCommand(context, options);
|
|
55
|
+
case 'trends':
|
|
56
|
+
return await handleTrendsCommand(context, options);
|
|
57
|
+
default:
|
|
58
|
+
console.error(`Unknown history subcommand: ${subcommand || '(none)'}`);
|
|
59
|
+
console.error(
|
|
60
|
+
'Available subcommands: list, show, compare, trends, clean, export',
|
|
61
|
+
);
|
|
62
|
+
return 2; // Config error
|
|
63
|
+
}
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.error(
|
|
66
|
+
'History command failed:',
|
|
67
|
+
error instanceof Error ? error.message : String(error),
|
|
68
|
+
);
|
|
69
|
+
return 2; // Configuration/runtime errors
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Format bytes in human-readable format
|
|
75
|
+
*/
|
|
76
|
+
const formatBytes = (bytes: number): string => {
|
|
77
|
+
const units = ['B', 'KB', 'MB', 'GB'];
|
|
78
|
+
let size = bytes;
|
|
79
|
+
let unitIndex = 0;
|
|
80
|
+
|
|
81
|
+
while (size >= 1024 && unitIndex < units.length - 1) {
|
|
82
|
+
size /= 1024;
|
|
83
|
+
unitIndex++;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return `${size.toFixed(1)} ${units[unitIndex]}`;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Handle the clean subcommand
|
|
91
|
+
*/
|
|
92
|
+
const handleCleanCommand = async (
|
|
93
|
+
context: CliContext,
|
|
94
|
+
options: HistoryOptions,
|
|
95
|
+
): Promise<number> => {
|
|
96
|
+
try {
|
|
97
|
+
// Build retention policy from arguments
|
|
98
|
+
const policy = {
|
|
99
|
+
...(options.maxAge && { maxAge: options.maxAge }),
|
|
100
|
+
...(options.maxRuns && { maxRuns: options.maxRuns }),
|
|
101
|
+
...(options.maxSize && { maxSize: options.maxSize }),
|
|
102
|
+
} as Partial<RetentionPolicy>;
|
|
103
|
+
|
|
104
|
+
if (Object.keys(policy).length === 0) {
|
|
105
|
+
console.error(
|
|
106
|
+
'At least one cleanup criterion must be specified (--max-age, --max-runs, or --max-size)',
|
|
107
|
+
);
|
|
108
|
+
return 2;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Show what would be cleaned unless confirmed
|
|
112
|
+
if (!options.confirm) {
|
|
113
|
+
console.log(
|
|
114
|
+
'This will clean up historical data based on the following policy:',
|
|
115
|
+
);
|
|
116
|
+
if (policy.maxAge) {
|
|
117
|
+
console.log(` - Remove runs older than ${policy.maxAge}ms`);
|
|
118
|
+
}
|
|
119
|
+
if (policy.maxRuns) {
|
|
120
|
+
console.log(` - Keep only the latest ${policy.maxRuns} runs`);
|
|
121
|
+
}
|
|
122
|
+
if (policy.maxSize) {
|
|
123
|
+
console.log(
|
|
124
|
+
` - Remove runs until storage is under ${policy.maxSize} bytes`,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
console.log();
|
|
128
|
+
console.log('Use --confirm to proceed with cleanup.');
|
|
129
|
+
return 0;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Perform cleanup
|
|
133
|
+
const result = await context.historyStorage.cleanup(policy);
|
|
134
|
+
|
|
135
|
+
if (!options.quiet) {
|
|
136
|
+
console.log(`Cleanup completed:`);
|
|
137
|
+
console.log(` Removed ${result.removedRuns} run(s)`);
|
|
138
|
+
console.log(` Freed ${formatBytes(result.freedBytes)} of storage`);
|
|
139
|
+
|
|
140
|
+
if (options.verbose && result.removedFiles.length > 0) {
|
|
141
|
+
console.log(` Removed files:`);
|
|
142
|
+
for (const file of result.removedFiles) {
|
|
143
|
+
console.log(` ${file}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return 0;
|
|
149
|
+
} catch (error) {
|
|
150
|
+
console.error(
|
|
151
|
+
'Failed to clean history:',
|
|
152
|
+
error instanceof Error ? error.message : String(error),
|
|
153
|
+
);
|
|
154
|
+
return 5;
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Handle the compare subcommand
|
|
160
|
+
*/
|
|
161
|
+
const handleCompareCommand = async (
|
|
162
|
+
context: CliContext,
|
|
163
|
+
options: HistoryOptions,
|
|
164
|
+
): Promise<number> => {
|
|
165
|
+
try {
|
|
166
|
+
// For compare command, IDs come from args after the subcommand
|
|
167
|
+
const id1 = options.args?.[0];
|
|
168
|
+
const id2 = options.args?.[1];
|
|
169
|
+
|
|
170
|
+
if (!id1 || !id2) {
|
|
171
|
+
console.error('Two run IDs are required for compare command');
|
|
172
|
+
console.error('Usage: modestbench history compare <run-id1> <run-id2>');
|
|
173
|
+
return 2;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const [run1, run2] = await Promise.all([
|
|
177
|
+
context.historyStorage.loadRun(id1),
|
|
178
|
+
context.historyStorage.loadRun(id2),
|
|
179
|
+
]);
|
|
180
|
+
|
|
181
|
+
if (!run1) {
|
|
182
|
+
console.error(`Run not found: ${id1}`);
|
|
183
|
+
return 1;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (!run2) {
|
|
187
|
+
console.error(`Run not found: ${id2}`);
|
|
188
|
+
return 1;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (options.format === 'json') {
|
|
192
|
+
const comparison = {
|
|
193
|
+
run1: { id: run1.id, summary: run1.summary },
|
|
194
|
+
run2: { id: run2.id, summary: run2.summary },
|
|
195
|
+
// TODO: Add detailed comparison logic
|
|
196
|
+
};
|
|
197
|
+
console.log(JSON.stringify(comparison, null, 2));
|
|
198
|
+
} else {
|
|
199
|
+
// Human format comparison
|
|
200
|
+
console.log(`Comparing runs:`);
|
|
201
|
+
console.log(` Run 1: ${run1.id} (${run1.startTime.toLocaleString()})`);
|
|
202
|
+
console.log(` Run 2: ${run2.id} (${run2.startTime.toLocaleString()})`);
|
|
203
|
+
console.log();
|
|
204
|
+
|
|
205
|
+
console.log('Summary comparison:');
|
|
206
|
+
console.log(
|
|
207
|
+
` Files: ${run1.summary.totalFiles} vs ${run2.summary.totalFiles}`,
|
|
208
|
+
);
|
|
209
|
+
console.log(
|
|
210
|
+
` Tasks: ${run1.summary.totalTasks} vs ${run2.summary.totalTasks}`,
|
|
211
|
+
);
|
|
212
|
+
console.log(
|
|
213
|
+
` Passed: ${run1.summary.passedTasks} vs ${run2.summary.passedTasks}`,
|
|
214
|
+
);
|
|
215
|
+
console.log(
|
|
216
|
+
` Failed: ${run1.summary.failedTasks} vs ${run2.summary.failedTasks}`,
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
// TODO: Add detailed performance comparison
|
|
220
|
+
console.log();
|
|
221
|
+
console.log('Note: Detailed performance comparison not yet implemented.');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return 0;
|
|
225
|
+
} catch (error) {
|
|
226
|
+
console.error(
|
|
227
|
+
'Failed to compare runs:',
|
|
228
|
+
error instanceof Error ? error.message : String(error),
|
|
229
|
+
);
|
|
230
|
+
return 5;
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Handle the export subcommand
|
|
236
|
+
*/
|
|
237
|
+
const handleExportCommand = async (
|
|
238
|
+
context: CliContext,
|
|
239
|
+
options: HistoryOptions,
|
|
240
|
+
): Promise<number> => {
|
|
241
|
+
try {
|
|
242
|
+
const format = options.format || 'json';
|
|
243
|
+
|
|
244
|
+
// Build query for export
|
|
245
|
+
const query = {
|
|
246
|
+
...(options.since && { since: parseDate(options.since) }),
|
|
247
|
+
...(options.until && { until: parseDate(options.until) }),
|
|
248
|
+
} as Partial<HistoryQuery>;
|
|
249
|
+
|
|
250
|
+
const exportData = await context.historyStorage.export(
|
|
251
|
+
format as 'csv' | 'json',
|
|
252
|
+
query,
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
if (options.outputDir) {
|
|
256
|
+
// Write to file
|
|
257
|
+
const fs = await import('node:fs/promises');
|
|
258
|
+
await fs.writeFile(options.outputDir, exportData, 'utf8');
|
|
259
|
+
if (!options.quiet) {
|
|
260
|
+
console.log(`Exported history to ${options.outputDir}`);
|
|
261
|
+
}
|
|
262
|
+
} else {
|
|
263
|
+
// Write to stdout
|
|
264
|
+
console.log(exportData);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return 0;
|
|
268
|
+
} catch (error) {
|
|
269
|
+
console.error(
|
|
270
|
+
'Failed to export history:',
|
|
271
|
+
error instanceof Error ? error.message : String(error),
|
|
272
|
+
);
|
|
273
|
+
return 5;
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Handle the list subcommand
|
|
279
|
+
*/
|
|
280
|
+
const handleListCommand = async (
|
|
281
|
+
context: CliContext,
|
|
282
|
+
options: HistoryOptions,
|
|
283
|
+
): Promise<number> => {
|
|
284
|
+
try {
|
|
285
|
+
// Build query from command line arguments
|
|
286
|
+
let parsedSince: Date | undefined;
|
|
287
|
+
let parsedUntil: Date | undefined;
|
|
288
|
+
|
|
289
|
+
if (options.since) {
|
|
290
|
+
try {
|
|
291
|
+
parsedSince = parseDate(options.since);
|
|
292
|
+
} catch (error) {
|
|
293
|
+
console.error(
|
|
294
|
+
'Invalid since date:',
|
|
295
|
+
error instanceof Error ? error.message : String(error),
|
|
296
|
+
);
|
|
297
|
+
return 2; // Invalid date format
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (options.until) {
|
|
302
|
+
try {
|
|
303
|
+
parsedUntil = parseDate(options.until);
|
|
304
|
+
} catch (error) {
|
|
305
|
+
console.error(
|
|
306
|
+
'Invalid until date:',
|
|
307
|
+
error instanceof Error ? error.message : String(error),
|
|
308
|
+
);
|
|
309
|
+
return 2; // Invalid date format
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const query = {
|
|
314
|
+
...(parsedSince && { since: parsedSince }),
|
|
315
|
+
...(parsedUntil && { until: parsedUntil }),
|
|
316
|
+
...(options.pattern && { pattern: options.pattern }),
|
|
317
|
+
...(options.tags && options.tags.length > 0 && { tags: options.tags }),
|
|
318
|
+
...(options.limit && { limit: options.limit }),
|
|
319
|
+
} as Partial<HistoryQuery>;
|
|
320
|
+
|
|
321
|
+
// Query historical runs
|
|
322
|
+
const runs = await context.historyStorage.queryRuns(query);
|
|
323
|
+
|
|
324
|
+
// Display results based on format
|
|
325
|
+
if (options.format === 'json') {
|
|
326
|
+
if (runs.length === 0) {
|
|
327
|
+
console.log('[]'); // Empty JSON array for no data
|
|
328
|
+
} else {
|
|
329
|
+
console.log(
|
|
330
|
+
JSON.stringify(
|
|
331
|
+
runs.map((run) => ({
|
|
332
|
+
duration: run.duration,
|
|
333
|
+
failed: run.summary.failedTasks,
|
|
334
|
+
files: run.summary.totalFiles,
|
|
335
|
+
id: run.id,
|
|
336
|
+
passed: run.summary.passedTasks,
|
|
337
|
+
startTime: run.startTime,
|
|
338
|
+
tasks: run.summary.totalTasks,
|
|
339
|
+
})),
|
|
340
|
+
null,
|
|
341
|
+
2,
|
|
342
|
+
),
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
} else if (options.format === 'csv') {
|
|
346
|
+
console.log('id,startTime,duration,files,tasks,passed,failed');
|
|
347
|
+
if (runs.length > 0) {
|
|
348
|
+
for (const run of runs) {
|
|
349
|
+
console.log(
|
|
350
|
+
`${run.id},${run.startTime.toISOString()},${run.duration},${run.summary.totalFiles},${run.summary.totalTasks},${run.summary.passedTasks},${run.summary.failedTasks}`,
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
// For CSV, no additional message needed - header is sufficient
|
|
355
|
+
} else {
|
|
356
|
+
// Human format
|
|
357
|
+
if (runs.length === 0) {
|
|
358
|
+
if (!options.quiet) {
|
|
359
|
+
console.log('No historical data found matching criteria.');
|
|
360
|
+
}
|
|
361
|
+
} else {
|
|
362
|
+
console.log('Recent benchmark runs:');
|
|
363
|
+
console.log();
|
|
364
|
+
|
|
365
|
+
for (const run of runs) {
|
|
366
|
+
const dateStr = run.startTime.toLocaleString();
|
|
367
|
+
const durationStr = `${(run.duration / 1000).toFixed(1)}s`;
|
|
368
|
+
const statusStr =
|
|
369
|
+
run.summary.failedTasks > 0
|
|
370
|
+
? `${run.summary.passedTasks} passed, ${run.summary.failedTasks} failed`
|
|
371
|
+
: `${run.summary.passedTasks} passed`;
|
|
372
|
+
|
|
373
|
+
console.log(
|
|
374
|
+
` ${run.id.substring(0, 8)} - ${dateStr} (${durationStr})`,
|
|
375
|
+
);
|
|
376
|
+
console.log(
|
|
377
|
+
` ${run.summary.totalFiles} files, ${run.summary.totalTasks} tasks: ${statusStr}`,
|
|
378
|
+
);
|
|
379
|
+
console.log();
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return 0;
|
|
385
|
+
} catch (error) {
|
|
386
|
+
console.error(
|
|
387
|
+
'Failed to list history:',
|
|
388
|
+
error instanceof Error ? error.message : String(error),
|
|
389
|
+
);
|
|
390
|
+
return 5;
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Handle the show subcommand
|
|
396
|
+
*/
|
|
397
|
+
const handleShowCommand = async (
|
|
398
|
+
context: CliContext,
|
|
399
|
+
options: HistoryOptions,
|
|
400
|
+
): Promise<number> => {
|
|
401
|
+
try {
|
|
402
|
+
// For show command, ID comes from the args after the subcommand
|
|
403
|
+
const runId = options.args?.[0];
|
|
404
|
+
|
|
405
|
+
if (!runId) {
|
|
406
|
+
console.error('Run ID is required for show command');
|
|
407
|
+
console.error('Usage: modestbench history show <run-id>');
|
|
408
|
+
return 2;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const run = await context.historyStorage.loadRun(runId);
|
|
412
|
+
|
|
413
|
+
if (!run) {
|
|
414
|
+
console.error(`Run not found: ${runId}`);
|
|
415
|
+
return 1;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (options.format === 'json') {
|
|
419
|
+
console.log(JSON.stringify(run, null, 2));
|
|
420
|
+
} else {
|
|
421
|
+
// Human format
|
|
422
|
+
console.log(`Benchmark Run: ${run.id}`);
|
|
423
|
+
console.log(`Date: ${run.startTime.toLocaleString()}`);
|
|
424
|
+
console.log(`Duration: ${(run.duration / 1000).toFixed(1)}s`);
|
|
425
|
+
console.log(
|
|
426
|
+
`Environment: Node.js ${run.environment.nodeVersion} on ${run.environment.platform}`,
|
|
427
|
+
);
|
|
428
|
+
|
|
429
|
+
if (run.git) {
|
|
430
|
+
console.log(`Git: ${run.git.branch}@${run.git.commit.substring(0, 8)}`);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
console.log();
|
|
434
|
+
console.log('Summary:');
|
|
435
|
+
console.log(` Files: ${run.summary.totalFiles}`);
|
|
436
|
+
console.log(` Suites: ${run.summary.totalSuites}`);
|
|
437
|
+
console.log(` Tasks: ${run.summary.totalTasks}`);
|
|
438
|
+
console.log(` Passed: ${run.summary.passedTasks}`);
|
|
439
|
+
console.log(` Failed: ${run.summary.failedTasks}`);
|
|
440
|
+
|
|
441
|
+
// TODO: Show detailed file/suite/task results
|
|
442
|
+
console.log();
|
|
443
|
+
console.log('Detailed results:');
|
|
444
|
+
for (const file of run.files) {
|
|
445
|
+
console.log(` 📁 ${file.filePath}`);
|
|
446
|
+
for (const suite of file.suites) {
|
|
447
|
+
console.log(` 📊 ${suite.name}`);
|
|
448
|
+
for (const task of suite.tasks) {
|
|
449
|
+
const status = task.error ? '❌' : '✅';
|
|
450
|
+
const timing = task.error
|
|
451
|
+
? 'failed'
|
|
452
|
+
: `${(task.mean / 1000000).toFixed(2)}ms`;
|
|
453
|
+
console.log(` ${status} ${task.name} - ${timing}`);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
return 0;
|
|
460
|
+
} catch (error) {
|
|
461
|
+
console.error(
|
|
462
|
+
'Failed to show run:',
|
|
463
|
+
error instanceof Error ? error.message : String(error),
|
|
464
|
+
);
|
|
465
|
+
return 5;
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Handle the trends subcommand
|
|
471
|
+
*/
|
|
472
|
+
const handleTrendsCommand = async (
|
|
473
|
+
context: CliContext,
|
|
474
|
+
options: HistoryOptions,
|
|
475
|
+
): Promise<number> => {
|
|
476
|
+
try {
|
|
477
|
+
// Build query from command line arguments (same as list command)
|
|
478
|
+
let parsedSince: Date | undefined;
|
|
479
|
+
let parsedUntil: Date | undefined;
|
|
480
|
+
|
|
481
|
+
if (options.since) {
|
|
482
|
+
try {
|
|
483
|
+
parsedSince = parseDate(options.since);
|
|
484
|
+
} catch (error) {
|
|
485
|
+
console.error(
|
|
486
|
+
'Invalid since date:',
|
|
487
|
+
error instanceof Error ? error.message : String(error),
|
|
488
|
+
);
|
|
489
|
+
return 2; // Invalid date format
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
if (options.until) {
|
|
494
|
+
try {
|
|
495
|
+
parsedUntil = parseDate(options.until);
|
|
496
|
+
} catch (error) {
|
|
497
|
+
console.error(
|
|
498
|
+
'Invalid until date:',
|
|
499
|
+
error instanceof Error ? error.message : String(error),
|
|
500
|
+
);
|
|
501
|
+
return 2; // Invalid date format
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// Get pattern from args or explicit pattern option
|
|
506
|
+
const pattern = options.args?.[0] || options.pattern;
|
|
507
|
+
|
|
508
|
+
const query = {
|
|
509
|
+
...(parsedSince && { since: parsedSince }),
|
|
510
|
+
...(parsedUntil && { until: parsedUntil }),
|
|
511
|
+
...(pattern && { pattern }),
|
|
512
|
+
...(options.tags && options.tags.length > 0 && { tags: options.tags }),
|
|
513
|
+
...(options.limit && { limit: options.limit }),
|
|
514
|
+
} as Partial<HistoryQuery>;
|
|
515
|
+
|
|
516
|
+
// Query historical runs
|
|
517
|
+
const runs = await context.historyStorage.queryRuns(query);
|
|
518
|
+
|
|
519
|
+
if (runs.length === 0) {
|
|
520
|
+
if (!options.quiet) {
|
|
521
|
+
console.log('No historical data found matching criteria');
|
|
522
|
+
}
|
|
523
|
+
return 0; // Success - no data is not an error
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
if (options.format === 'json') {
|
|
527
|
+
// TODO: Generate trends data in JSON format
|
|
528
|
+
const trendsData = {
|
|
529
|
+
runs: runs.length,
|
|
530
|
+
timespan: {
|
|
531
|
+
end: runs[0]?.startTime,
|
|
532
|
+
start: runs[runs.length - 1]?.startTime,
|
|
533
|
+
},
|
|
534
|
+
// TODO: Add actual trend calculations
|
|
535
|
+
};
|
|
536
|
+
console.log(JSON.stringify(trendsData, null, 2));
|
|
537
|
+
} else {
|
|
538
|
+
// Human format trends
|
|
539
|
+
if (!options.quiet) {
|
|
540
|
+
console.log(`Performance trends for ${runs.length} runs:`);
|
|
541
|
+
console.log(
|
|
542
|
+
`Time range: ${runs[runs.length - 1]?.startTime} to ${runs[0]?.startTime}`,
|
|
543
|
+
);
|
|
544
|
+
// TODO: Add trend analysis and visualization
|
|
545
|
+
console.log('(Trend analysis not yet implemented)');
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
return 0; // Success
|
|
550
|
+
} catch (error) {
|
|
551
|
+
console.error('Error showing trends:', error);
|
|
552
|
+
return 3; // Runtime error
|
|
553
|
+
}
|
|
554
|
+
};
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* Parse date string (ISO 8601 or relative)
|
|
558
|
+
*/
|
|
559
|
+
const parseDate = (dateStr: string): Date => {
|
|
560
|
+
// Try parsing as ISO 8601 first
|
|
561
|
+
const isoDate = new Date(dateStr);
|
|
562
|
+
if (!isNaN(isoDate.getTime())) {
|
|
563
|
+
return isoDate;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// TODO: Parse relative dates like "1 week ago", "3 days ago", etc.
|
|
567
|
+
// For now, throw error for invalid dates
|
|
568
|
+
throw new Error(`Invalid date format: "${dateStr}"`);
|
|
569
|
+
};
|