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