modestbench 0.9.1 → 0.9.2
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 +8 -0
- package/dist/cli/builder.cjs +259 -0
- package/dist/cli/builder.cjs.map +1 -0
- package/dist/cli/builder.d.cts +37 -0
- package/dist/cli/builder.d.cts.map +1 -0
- package/dist/cli/builder.d.ts +37 -0
- package/dist/cli/builder.d.ts.map +1 -0
- package/dist/cli/builder.js +255 -0
- package/dist/cli/builder.js.map +1 -0
- package/dist/cli/commands/baseline.cjs +4 -33
- package/dist/cli/commands/baseline.cjs.map +1 -1
- package/dist/cli/commands/baseline.d.cts.map +1 -1
- package/dist/cli/commands/baseline.d.ts.map +1 -1
- package/dist/cli/commands/baseline.js +2 -31
- package/dist/cli/commands/baseline.js.map +1 -1
- package/dist/cli/commands/history.cjs +2 -14
- package/dist/cli/commands/history.cjs.map +1 -1
- package/dist/cli/commands/history.d.cts.map +1 -1
- package/dist/cli/commands/history.d.ts.map +1 -1
- package/dist/cli/commands/history.js +1 -13
- package/dist/cli/commands/history.js.map +1 -1
- package/dist/cli/context.cjs +60 -0
- package/dist/cli/context.cjs.map +1 -0
- package/dist/cli/context.d.cts +28 -0
- package/dist/cli/context.d.cts.map +1 -0
- package/dist/cli/context.d.ts +28 -0
- package/dist/cli/context.d.ts.map +1 -0
- package/dist/cli/context.js +56 -0
- package/dist/cli/context.js.map +1 -0
- package/dist/cli/handlers.cjs +74 -0
- package/dist/cli/handlers.cjs.map +1 -0
- package/dist/cli/handlers.d.cts +13 -0
- package/dist/cli/handlers.d.cts.map +1 -0
- package/dist/cli/handlers.d.ts +13 -0
- package/dist/cli/handlers.d.ts.map +1 -0
- package/dist/cli/handlers.js +70 -0
- package/dist/cli/handlers.js.map +1 -0
- package/dist/cli/index.cjs +12 -724
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.d.cts +4 -39
- package/dist/cli/index.d.cts.map +1 -1
- package/dist/cli/index.d.ts +4 -39
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +9 -722
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/parsers/analyze.cjs +54 -0
- package/dist/cli/parsers/analyze.cjs.map +1 -0
- package/dist/cli/parsers/analyze.d.cts +37 -0
- package/dist/cli/parsers/analyze.d.cts.map +1 -0
- package/dist/cli/parsers/analyze.d.ts +37 -0
- package/dist/cli/parsers/analyze.d.ts.map +1 -0
- package/dist/cli/parsers/analyze.js +51 -0
- package/dist/cli/parsers/analyze.js.map +1 -0
- package/dist/cli/parsers/baseline.cjs +75 -0
- package/dist/cli/parsers/baseline.cjs.map +1 -0
- package/dist/cli/parsers/baseline.d.cts +59 -0
- package/dist/cli/parsers/baseline.d.cts.map +1 -0
- package/dist/cli/parsers/baseline.d.ts +59 -0
- package/dist/cli/parsers/baseline.d.ts.map +1 -0
- package/dist/cli/parsers/baseline.js +72 -0
- package/dist/cli/parsers/baseline.js.map +1 -0
- package/dist/cli/parsers/global.cjs +49 -0
- package/dist/cli/parsers/global.cjs.map +1 -0
- package/dist/cli/parsers/global.d.cts +45 -0
- package/dist/cli/parsers/global.d.cts.map +1 -0
- package/dist/cli/parsers/global.d.ts +45 -0
- package/dist/cli/parsers/global.d.ts.map +1 -0
- package/dist/cli/parsers/global.js +46 -0
- package/dist/cli/parsers/global.js.map +1 -0
- package/dist/cli/parsers/history.cjs +138 -0
- package/dist/cli/parsers/history.cjs.map +1 -0
- package/dist/cli/parsers/history.d.cts +108 -0
- package/dist/cli/parsers/history.d.cts.map +1 -0
- package/dist/cli/parsers/history.d.ts +108 -0
- package/dist/cli/parsers/history.d.ts.map +1 -0
- package/dist/cli/parsers/history.js +135 -0
- package/dist/cli/parsers/history.js.map +1 -0
- package/dist/cli/parsers/index.cjs +35 -0
- package/dist/cli/parsers/index.cjs.map +1 -0
- package/dist/cli/parsers/index.d.cts +15 -0
- package/dist/cli/parsers/index.d.cts.map +1 -0
- package/dist/cli/parsers/index.d.ts +15 -0
- package/dist/cli/parsers/index.d.ts.map +1 -0
- package/dist/cli/parsers/index.js +15 -0
- package/dist/cli/parsers/index.js.map +1 -0
- package/dist/cli/parsers/init.cjs +39 -0
- package/dist/cli/parsers/init.cjs.map +1 -0
- package/dist/cli/parsers/init.d.cts +32 -0
- package/dist/cli/parsers/init.d.cts.map +1 -0
- package/dist/cli/parsers/init.d.ts +32 -0
- package/dist/cli/parsers/init.d.ts.map +1 -0
- package/dist/cli/parsers/init.js +36 -0
- package/dist/cli/parsers/init.js.map +1 -0
- package/dist/cli/parsers/run.cjs +99 -0
- package/dist/cli/parsers/run.cjs.map +1 -0
- package/dist/cli/parsers/run.d.cts +62 -0
- package/dist/cli/parsers/run.d.cts.map +1 -0
- package/dist/cli/parsers/run.d.ts +62 -0
- package/dist/cli/parsers/run.d.ts.map +1 -0
- package/dist/cli/parsers/run.js +96 -0
- package/dist/cli/parsers/run.js.map +1 -0
- package/dist/cli/parsers/test.cjs +42 -0
- package/dist/cli/parsers/test.cjs.map +1 -0
- package/dist/cli/parsers/test.d.cts +31 -0
- package/dist/cli/parsers/test.d.cts.map +1 -0
- package/dist/cli/parsers/test.d.ts +31 -0
- package/dist/cli/parsers/test.d.ts.map +1 -0
- package/dist/cli/parsers/test.js +39 -0
- package/dist/cli/parsers/test.js.map +1 -0
- package/dist/cli/theme.cjs +35 -0
- package/dist/cli/theme.cjs.map +1 -0
- package/dist/cli/theme.d.cts +31 -0
- package/dist/cli/theme.d.cts.map +1 -0
- package/dist/cli/theme.d.ts +31 -0
- package/dist/cli/theme.d.ts.map +1 -0
- package/dist/cli/theme.js +32 -0
- package/dist/cli/theme.js.map +1 -0
- package/dist/errors/base.cjs +3 -12
- package/dist/errors/base.cjs.map +1 -1
- package/dist/errors/base.d.cts +0 -7
- package/dist/errors/base.d.cts.map +1 -1
- package/dist/errors/base.d.ts +0 -7
- package/dist/errors/base.d.ts.map +1 -1
- package/dist/errors/base.js +1 -9
- package/dist/errors/base.js.map +1 -1
- package/dist/services/profiler/profile-runner.cjs +11 -0
- package/dist/services/profiler/profile-runner.cjs.map +1 -1
- package/dist/services/profiler/profile-runner.d.cts +2 -0
- package/dist/services/profiler/profile-runner.d.cts.map +1 -1
- package/dist/services/profiler/profile-runner.d.ts +2 -0
- package/dist/services/profiler/profile-runner.d.ts.map +1 -1
- package/dist/services/profiler/profile-runner.js +11 -0
- package/dist/services/profiler/profile-runner.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/builder.ts +387 -0
- package/src/cli/commands/baseline.ts +7 -33
- package/src/cli/commands/history.ts +1 -16
- package/src/cli/context.ts +117 -0
- package/src/cli/handlers.ts +76 -0
- package/src/cli/index.ts +10 -1012
- package/src/cli/parsers/analyze.ts +61 -0
- package/src/cli/parsers/baseline.ts +92 -0
- package/src/cli/parsers/global.ts +51 -0
- package/src/cli/parsers/history.ts +168 -0
- package/src/cli/parsers/index.ts +28 -0
- package/src/cli/parsers/init.ts +45 -0
- package/src/cli/parsers/run.ts +118 -0
- package/src/cli/parsers/test.ts +46 -0
- package/src/cli/theme.ts +33 -0
- package/src/errors/base.ts +1 -10
- package/src/services/profiler/profile-runner.ts +15 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Analyze Command Parser
|
|
3
|
+
*
|
|
4
|
+
* Parser for the `analyze` command (aliased as `profile`) which profiles code
|
|
5
|
+
* execution to identify benchmark candidates.
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { camelCaseValues, map, merge, opt, pos } from '@boneskull/bargs';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Base parser for `analyze` command options and positionals
|
|
14
|
+
*/
|
|
15
|
+
const analyzeParserBase = merge(
|
|
16
|
+
opt.options({
|
|
17
|
+
'filter-file': opt.string({
|
|
18
|
+
description: 'Filter functions by file glob pattern',
|
|
19
|
+
}),
|
|
20
|
+
'group-by-file': opt.boolean({
|
|
21
|
+
description: 'Group results by file',
|
|
22
|
+
}),
|
|
23
|
+
input: opt.string({
|
|
24
|
+
aliases: ['i'],
|
|
25
|
+
description: 'Path to existing *.cpuprofile file',
|
|
26
|
+
}),
|
|
27
|
+
'min-percent': opt.number({
|
|
28
|
+
aliases: ['m', 'min'],
|
|
29
|
+
default: 0.5,
|
|
30
|
+
description: 'Minimum execution percentage to show',
|
|
31
|
+
}),
|
|
32
|
+
top: opt.number({
|
|
33
|
+
aliases: ['n'],
|
|
34
|
+
default: 25,
|
|
35
|
+
description: 'Number of top functions to show',
|
|
36
|
+
}),
|
|
37
|
+
}),
|
|
38
|
+
pos.positionals(
|
|
39
|
+
pos.string({
|
|
40
|
+
description: 'Command to analyze (e.g., "npm test")',
|
|
41
|
+
name: 'command',
|
|
42
|
+
}),
|
|
43
|
+
),
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Parser for `analyze` command with validation and camelCase values
|
|
48
|
+
*
|
|
49
|
+
* Validates that either a command or --input is provided. Uses camelCaseValues
|
|
50
|
+
* transform for cleaner property access.
|
|
51
|
+
*/
|
|
52
|
+
export const analyzeParser = map(
|
|
53
|
+
map(analyzeParserBase, ({ positionals, values }) => {
|
|
54
|
+
const [command] = positionals;
|
|
55
|
+
if (!command && !values.input) {
|
|
56
|
+
throw new Error('Either [command] or --input must be provided');
|
|
57
|
+
}
|
|
58
|
+
return { positionals, values };
|
|
59
|
+
}),
|
|
60
|
+
camelCaseValues,
|
|
61
|
+
);
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Baseline Command Parsers
|
|
3
|
+
*
|
|
4
|
+
* Parsers for the `baseline` command and its subcommands: set, list, show,
|
|
5
|
+
* delete, and analyze.
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { camelCaseValues, map, merge, opt, pos } from '@boneskull/bargs';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Parser for `baseline set` command
|
|
14
|
+
*
|
|
15
|
+
* Uses camelCaseValues transform for cleaner property access.
|
|
16
|
+
*/
|
|
17
|
+
export const baselineSetParser = map(
|
|
18
|
+
merge(
|
|
19
|
+
opt.options({
|
|
20
|
+
branch: opt.string({
|
|
21
|
+
description: 'Git branch name',
|
|
22
|
+
}),
|
|
23
|
+
commit: opt.string({
|
|
24
|
+
description: 'Git commit SHA (40 characters)',
|
|
25
|
+
}),
|
|
26
|
+
default: opt.boolean({
|
|
27
|
+
description: 'Set as default baseline',
|
|
28
|
+
}),
|
|
29
|
+
'run-id': opt.string({
|
|
30
|
+
description: 'Specific run ID to save (default: most recent)',
|
|
31
|
+
}),
|
|
32
|
+
}),
|
|
33
|
+
pos.positionals(
|
|
34
|
+
pos.string({
|
|
35
|
+
description: 'Name for the baseline',
|
|
36
|
+
name: 'name',
|
|
37
|
+
required: true,
|
|
38
|
+
}),
|
|
39
|
+
),
|
|
40
|
+
),
|
|
41
|
+
camelCaseValues,
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Parser for `baseline list` command
|
|
46
|
+
*/
|
|
47
|
+
export const baselineListParser = opt.options({
|
|
48
|
+
format: opt.enum(['human', 'json'] as const, {
|
|
49
|
+
description: 'Output format',
|
|
50
|
+
}),
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Parser for `baseline show` command
|
|
55
|
+
*/
|
|
56
|
+
export const baselineShowParser = merge(
|
|
57
|
+
opt.options({
|
|
58
|
+
format: opt.enum(['human', 'json'] as const, {
|
|
59
|
+
description: 'Output format',
|
|
60
|
+
}),
|
|
61
|
+
}),
|
|
62
|
+
pos.positionals(
|
|
63
|
+
pos.string({
|
|
64
|
+
description: 'Baseline name to show',
|
|
65
|
+
name: 'name',
|
|
66
|
+
required: true,
|
|
67
|
+
}),
|
|
68
|
+
),
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Parser for `baseline delete` command
|
|
73
|
+
*/
|
|
74
|
+
export const baselineDeleteParser = pos.positionals(
|
|
75
|
+
pos.string({
|
|
76
|
+
description: 'Baseline name to delete',
|
|
77
|
+
name: 'name',
|
|
78
|
+
required: true,
|
|
79
|
+
}),
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Parser for `baseline analyze` command
|
|
84
|
+
*/
|
|
85
|
+
export const baselineAnalyzeParser = opt.options({
|
|
86
|
+
confidence: opt.number({
|
|
87
|
+
description: 'Confidence level (0.5-0.999, default 0.95)',
|
|
88
|
+
}),
|
|
89
|
+
runs: opt.number({
|
|
90
|
+
description: 'Number of recent runs to analyze',
|
|
91
|
+
}),
|
|
92
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global CLI Options
|
|
3
|
+
*
|
|
4
|
+
* Options shared across all commands (verbose, config, cwd, etc.) and common
|
|
5
|
+
* option fragments used by subcommands.
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { camelCaseValues, map, opt } from '@boneskull/bargs';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Global options available to all commands
|
|
14
|
+
*
|
|
15
|
+
* Uses camelCaseValues transform so handlers can use `values.noColor` instead
|
|
16
|
+
* of `values['no-color']`.
|
|
17
|
+
*/
|
|
18
|
+
export const globalOptions = map(
|
|
19
|
+
opt.options({
|
|
20
|
+
config: opt.string({
|
|
21
|
+
aliases: ['c'],
|
|
22
|
+
description: 'Path to configuration file',
|
|
23
|
+
}),
|
|
24
|
+
cwd: opt.string({
|
|
25
|
+
description: 'Working directory',
|
|
26
|
+
}),
|
|
27
|
+
json: opt.boolean({
|
|
28
|
+
description: 'Output results in JSON format',
|
|
29
|
+
}),
|
|
30
|
+
'no-color': opt.boolean({
|
|
31
|
+
description: 'Disable colored output',
|
|
32
|
+
}),
|
|
33
|
+
progress: opt.boolean({
|
|
34
|
+
description: 'Show animated progress bar',
|
|
35
|
+
}),
|
|
36
|
+
verbose: opt.boolean({
|
|
37
|
+
aliases: ['v'],
|
|
38
|
+
description: 'Enable verbose output',
|
|
39
|
+
}),
|
|
40
|
+
}),
|
|
41
|
+
camelCaseValues,
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Additional global options for history and baseline subcommands
|
|
46
|
+
*/
|
|
47
|
+
export const quietOption = opt.options({
|
|
48
|
+
quiet: opt.boolean({
|
|
49
|
+
description: 'Minimal output',
|
|
50
|
+
}),
|
|
51
|
+
});
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* History Command Parsers
|
|
3
|
+
*
|
|
4
|
+
* Parsers for the `history` command and its subcommands: list, show, compare,
|
|
5
|
+
* trends, clean, and export.
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { camelCaseValues, map, merge, opt, pos } from '@boneskull/bargs';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Parser for `history list` command
|
|
14
|
+
*/
|
|
15
|
+
export const historyListParser = opt.options({
|
|
16
|
+
format: opt.enum(['human', 'json', 'csv'] as const, {
|
|
17
|
+
description: 'Output format',
|
|
18
|
+
}),
|
|
19
|
+
limit: opt.number({
|
|
20
|
+
description: 'Maximum number of results',
|
|
21
|
+
}),
|
|
22
|
+
pattern: opt.string({
|
|
23
|
+
description: 'Filter by benchmark name pattern',
|
|
24
|
+
}),
|
|
25
|
+
since: opt.string({
|
|
26
|
+
description:
|
|
27
|
+
'Show runs since date (ISO 8601 or relative like "1 week ago")',
|
|
28
|
+
}),
|
|
29
|
+
tag: opt.array('string', {
|
|
30
|
+
aliases: ['t'],
|
|
31
|
+
description: 'Filter by tags',
|
|
32
|
+
}),
|
|
33
|
+
until: opt.string({
|
|
34
|
+
description: 'Show runs until date (ISO 8601 or relative like "1 day ago")',
|
|
35
|
+
}),
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Parser for `history show` command
|
|
40
|
+
*/
|
|
41
|
+
export const historyShowParser = merge(
|
|
42
|
+
opt.options({
|
|
43
|
+
format: opt.enum(['human', 'json', 'csv'] as const, {
|
|
44
|
+
description: 'Output format',
|
|
45
|
+
}),
|
|
46
|
+
}),
|
|
47
|
+
pos.positionals(
|
|
48
|
+
pos.string({
|
|
49
|
+
description: 'ID of the benchmark run to show',
|
|
50
|
+
name: 'run-id',
|
|
51
|
+
required: true,
|
|
52
|
+
}),
|
|
53
|
+
),
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Parser for `history compare` command
|
|
58
|
+
*/
|
|
59
|
+
export const historyCompareParser = merge(
|
|
60
|
+
opt.options({
|
|
61
|
+
format: opt.enum(['human', 'json'] as const, {
|
|
62
|
+
description: 'Output format',
|
|
63
|
+
}),
|
|
64
|
+
}),
|
|
65
|
+
pos.positionals(
|
|
66
|
+
pos.string({
|
|
67
|
+
description: 'ID of the first benchmark run',
|
|
68
|
+
name: 'run-id1',
|
|
69
|
+
required: true,
|
|
70
|
+
}),
|
|
71
|
+
pos.string({
|
|
72
|
+
description: 'ID of the second benchmark run',
|
|
73
|
+
name: 'run-id2',
|
|
74
|
+
required: true,
|
|
75
|
+
}),
|
|
76
|
+
),
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Parser for `history trends` command
|
|
81
|
+
*/
|
|
82
|
+
export const historyTrendsParser = merge(
|
|
83
|
+
opt.options({
|
|
84
|
+
all: opt.boolean({
|
|
85
|
+
aliases: ['a'],
|
|
86
|
+
description: 'Analyze all runs (ignore limit)',
|
|
87
|
+
}),
|
|
88
|
+
format: opt.enum(['human', 'json'] as const, {
|
|
89
|
+
description: 'Output format',
|
|
90
|
+
}),
|
|
91
|
+
limit: opt.number({
|
|
92
|
+
description: 'Maximum number of runs to analyze',
|
|
93
|
+
}),
|
|
94
|
+
since: opt.string({
|
|
95
|
+
description:
|
|
96
|
+
'Show trends since date (ISO 8601 or relative like "1 week ago")',
|
|
97
|
+
}),
|
|
98
|
+
tag: opt.array('string', {
|
|
99
|
+
aliases: ['t'],
|
|
100
|
+
description: 'Filter by tags',
|
|
101
|
+
}),
|
|
102
|
+
until: opt.string({
|
|
103
|
+
description:
|
|
104
|
+
'Show trends until date (ISO 8601 or relative like "1 day ago")',
|
|
105
|
+
}),
|
|
106
|
+
}),
|
|
107
|
+
pos.positionals(
|
|
108
|
+
pos.string({
|
|
109
|
+
description: 'Filter by benchmark name pattern',
|
|
110
|
+
name: 'pattern',
|
|
111
|
+
}),
|
|
112
|
+
),
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Parser for `history clean` command
|
|
117
|
+
*
|
|
118
|
+
* Includes validation requiring at least one cleanup criterion. Uses
|
|
119
|
+
* camelCaseValues transform for cleaner property access.
|
|
120
|
+
*/
|
|
121
|
+
export const historyCleanParser = map(
|
|
122
|
+
map(
|
|
123
|
+
opt.options({
|
|
124
|
+
'max-age': opt.number({
|
|
125
|
+
description: 'Remove runs older than this many days',
|
|
126
|
+
}),
|
|
127
|
+
'max-runs': opt.number({
|
|
128
|
+
description: 'Keep only this many most recent runs',
|
|
129
|
+
}),
|
|
130
|
+
'max-size': opt.number({
|
|
131
|
+
description: 'Keep history under this size in bytes',
|
|
132
|
+
}),
|
|
133
|
+
yes: opt.boolean({
|
|
134
|
+
aliases: ['y'],
|
|
135
|
+
description: 'Confirm cleanup without prompting',
|
|
136
|
+
}),
|
|
137
|
+
}),
|
|
138
|
+
({ positionals, values }) => {
|
|
139
|
+
if (!values['max-age'] && !values['max-runs'] && !values['max-size']) {
|
|
140
|
+
throw new Error(
|
|
141
|
+
'At least one cleanup criterion must be specified (--max-age, --max-runs, or --max-size)',
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
return { positionals, values };
|
|
145
|
+
},
|
|
146
|
+
),
|
|
147
|
+
camelCaseValues,
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Parser for `history export` command
|
|
152
|
+
*/
|
|
153
|
+
export const historyExportParser = opt.options({
|
|
154
|
+
format: opt.enum(['json', 'csv'] as const, {
|
|
155
|
+
description: 'Export format',
|
|
156
|
+
}),
|
|
157
|
+
output: opt.string({
|
|
158
|
+
aliases: ['o'],
|
|
159
|
+
description: 'Output file path',
|
|
160
|
+
required: true,
|
|
161
|
+
}),
|
|
162
|
+
since: opt.string({
|
|
163
|
+
description: 'Export runs since date',
|
|
164
|
+
}),
|
|
165
|
+
until: opt.string({
|
|
166
|
+
description: 'Export runs until date',
|
|
167
|
+
}),
|
|
168
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Parsers
|
|
3
|
+
*
|
|
4
|
+
* Re-exports all bargs parser definitions for CLI commands.
|
|
5
|
+
*
|
|
6
|
+
* @packageDocumentation
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export { analyzeParser } from './analyze.js';
|
|
10
|
+
export {
|
|
11
|
+
baselineAnalyzeParser,
|
|
12
|
+
baselineDeleteParser,
|
|
13
|
+
baselineListParser,
|
|
14
|
+
baselineSetParser,
|
|
15
|
+
baselineShowParser,
|
|
16
|
+
} from './baseline.js';
|
|
17
|
+
export { globalOptions, quietOption } from './global.js';
|
|
18
|
+
export {
|
|
19
|
+
historyCleanParser,
|
|
20
|
+
historyCompareParser,
|
|
21
|
+
historyExportParser,
|
|
22
|
+
historyListParser,
|
|
23
|
+
historyShowParser,
|
|
24
|
+
historyTrendsParser,
|
|
25
|
+
} from './history.js';
|
|
26
|
+
export { initParser } from './init.js';
|
|
27
|
+
export { runParser } from './run.js';
|
|
28
|
+
export { testParser } from './test.js';
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Init Command Parser
|
|
3
|
+
*
|
|
4
|
+
* Parser for the `init` command which initializes a new benchmark project.
|
|
5
|
+
*
|
|
6
|
+
* @packageDocumentation
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { camelCaseValues, map, merge, opt, pos } from '@boneskull/bargs';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Parser for `init` command
|
|
13
|
+
*
|
|
14
|
+
* Uses camelCaseValues transform for cleaner property access.
|
|
15
|
+
*/
|
|
16
|
+
export const initParser = map(
|
|
17
|
+
merge(
|
|
18
|
+
opt.options({
|
|
19
|
+
'config-type': opt.enum(['json', 'yaml', 'js', 'ts'] as const, {
|
|
20
|
+
description: 'Configuration file format',
|
|
21
|
+
}),
|
|
22
|
+
examples: opt.boolean({
|
|
23
|
+
description: 'Include example benchmark files',
|
|
24
|
+
}),
|
|
25
|
+
force: opt.boolean({
|
|
26
|
+
description: 'Overwrite existing files',
|
|
27
|
+
}),
|
|
28
|
+
quiet: opt.boolean({
|
|
29
|
+
aliases: ['q'],
|
|
30
|
+
description: 'Minimal output',
|
|
31
|
+
}),
|
|
32
|
+
yes: opt.boolean({
|
|
33
|
+
aliases: ['y'],
|
|
34
|
+
description: 'Accept all prompts automatically',
|
|
35
|
+
}),
|
|
36
|
+
}),
|
|
37
|
+
pos.positionals(
|
|
38
|
+
pos.enum(['basic', 'advanced', 'library'] as const, {
|
|
39
|
+
description: 'Type of project to initialize',
|
|
40
|
+
name: 'type',
|
|
41
|
+
}),
|
|
42
|
+
),
|
|
43
|
+
),
|
|
44
|
+
camelCaseValues,
|
|
45
|
+
);
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run Command Parser
|
|
3
|
+
*
|
|
4
|
+
* Parser for the `run` command which executes benchmark files.
|
|
5
|
+
*
|
|
6
|
+
* @packageDocumentation
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { camelCaseValues, map, merge, opt, pos } from '@boneskull/bargs';
|
|
10
|
+
|
|
11
|
+
import { DEFAULT_REPORTER, Engines, Reporters } from '../../constants.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Base parser for `run` command options and positionals
|
|
15
|
+
*/
|
|
16
|
+
const runParserBase = merge(
|
|
17
|
+
opt.options({
|
|
18
|
+
bail: opt.boolean({
|
|
19
|
+
aliases: ['b'],
|
|
20
|
+
description: 'Stop on first failure',
|
|
21
|
+
}),
|
|
22
|
+
engine: opt.enum([Engines.TINYBENCH, Engines.ACCURATE] as const, {
|
|
23
|
+
aliases: ['e'],
|
|
24
|
+
description:
|
|
25
|
+
'Benchmark engine: tinybench (default) or accurate (requires --allow-natives-syntax)',
|
|
26
|
+
}),
|
|
27
|
+
exclude: opt.array('string', {
|
|
28
|
+
aliases: ['X'],
|
|
29
|
+
description: 'Exclude patterns',
|
|
30
|
+
}),
|
|
31
|
+
'exclude-tag': opt.array('string', {
|
|
32
|
+
aliases: ['T'],
|
|
33
|
+
description: 'Exclude benchmarks with any of these tags',
|
|
34
|
+
}),
|
|
35
|
+
iterations: opt.number({
|
|
36
|
+
aliases: ['i'],
|
|
37
|
+
description: 'Number of iterations per benchmark',
|
|
38
|
+
}),
|
|
39
|
+
'json-pretty': opt.boolean({
|
|
40
|
+
description: 'Pretty-print JSON output (only affects json reporter)',
|
|
41
|
+
}),
|
|
42
|
+
'limit-by': opt.enum(['time', 'iterations', 'any', 'all'] as const, {
|
|
43
|
+
aliases: ['l', 'limit'],
|
|
44
|
+
description:
|
|
45
|
+
'How to limit benchmarks: time (time budget), iterations (sample count), any (either threshold), all (both thresholds)',
|
|
46
|
+
}),
|
|
47
|
+
output: opt.string({
|
|
48
|
+
aliases: ['o'],
|
|
49
|
+
description: 'Output directory for reports',
|
|
50
|
+
}),
|
|
51
|
+
'output-file': opt.string({
|
|
52
|
+
aliases: ['of', 'file'],
|
|
53
|
+
description:
|
|
54
|
+
'Custom filename for reporter output (use with single reporter only)',
|
|
55
|
+
}),
|
|
56
|
+
quiet: opt.boolean({
|
|
57
|
+
aliases: ['q'],
|
|
58
|
+
description: 'Minimal output',
|
|
59
|
+
}),
|
|
60
|
+
reporter: opt.array(
|
|
61
|
+
[
|
|
62
|
+
Reporters.HUMAN,
|
|
63
|
+
Reporters.JSON,
|
|
64
|
+
Reporters.CSV,
|
|
65
|
+
Reporters.NYAN,
|
|
66
|
+
Reporters.SIMPLE,
|
|
67
|
+
] as const,
|
|
68
|
+
{
|
|
69
|
+
aliases: ['r'],
|
|
70
|
+
default: [DEFAULT_REPORTER],
|
|
71
|
+
description: 'Output reporters to use (human,json,csv)',
|
|
72
|
+
},
|
|
73
|
+
),
|
|
74
|
+
tag: opt.array('string', {
|
|
75
|
+
description: 'Include only benchmarks with any of these tags',
|
|
76
|
+
}),
|
|
77
|
+
time: opt.number({
|
|
78
|
+
aliases: ['t'],
|
|
79
|
+
description: 'Time budget per benchmark in milliseconds',
|
|
80
|
+
}),
|
|
81
|
+
timeout: opt.number({
|
|
82
|
+
description: 'Timeout per benchmark in milliseconds',
|
|
83
|
+
}),
|
|
84
|
+
warmup: opt.number({
|
|
85
|
+
aliases: ['w', 'warm'],
|
|
86
|
+
description: 'Number of warmup iterations',
|
|
87
|
+
}),
|
|
88
|
+
}),
|
|
89
|
+
pos.positionals(
|
|
90
|
+
pos.variadic('string', {
|
|
91
|
+
description:
|
|
92
|
+
'File paths, directory paths, or glob patterns for benchmark files',
|
|
93
|
+
name: 'pattern',
|
|
94
|
+
}),
|
|
95
|
+
),
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Parser for `run` command with validation and camelCase values
|
|
100
|
+
*
|
|
101
|
+
* Validates that --output-file is only used with a single reporter. Uses
|
|
102
|
+
* camelCaseValues transform for cleaner property access.
|
|
103
|
+
*/
|
|
104
|
+
export const runParser = map(
|
|
105
|
+
map(runParserBase, ({ positionals, values }) => {
|
|
106
|
+
if (
|
|
107
|
+
values.reporter &&
|
|
108
|
+
values.reporter.length > 1 &&
|
|
109
|
+
values['output-file']
|
|
110
|
+
) {
|
|
111
|
+
throw new Error(
|
|
112
|
+
'--output-file can only be used with a single reporter. Use --output <dir> for multiple reporters.',
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
return { positionals, values };
|
|
116
|
+
}),
|
|
117
|
+
camelCaseValues,
|
|
118
|
+
);
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test Command Parser
|
|
3
|
+
*
|
|
4
|
+
* Parser for the `test` command which runs test files as benchmarks.
|
|
5
|
+
*
|
|
6
|
+
* @packageDocumentation
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { merge, opt, pos } from '@boneskull/bargs';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Parser for `test` command
|
|
13
|
+
*/
|
|
14
|
+
export const testParser = merge(
|
|
15
|
+
opt.options({
|
|
16
|
+
bail: opt.boolean({
|
|
17
|
+
aliases: ['b'],
|
|
18
|
+
description: 'Stop on first failure',
|
|
19
|
+
}),
|
|
20
|
+
iterations: opt.number({
|
|
21
|
+
aliases: ['i'],
|
|
22
|
+
default: 100,
|
|
23
|
+
description: 'Number of iterations per test',
|
|
24
|
+
}),
|
|
25
|
+
quiet: opt.boolean({
|
|
26
|
+
aliases: ['q'],
|
|
27
|
+
description: 'Minimal output',
|
|
28
|
+
}),
|
|
29
|
+
warmup: opt.number({
|
|
30
|
+
aliases: ['w'],
|
|
31
|
+
default: 5,
|
|
32
|
+
description: 'Number of warmup iterations',
|
|
33
|
+
}),
|
|
34
|
+
}),
|
|
35
|
+
pos.positionals(
|
|
36
|
+
pos.enum(['ava', 'jest', 'mocha', 'node-test'] as const, {
|
|
37
|
+
description: 'Test framework to use',
|
|
38
|
+
name: 'framework',
|
|
39
|
+
required: true,
|
|
40
|
+
}),
|
|
41
|
+
pos.variadic('string', {
|
|
42
|
+
description: 'Test file paths or glob patterns',
|
|
43
|
+
name: 'files',
|
|
44
|
+
}),
|
|
45
|
+
),
|
|
46
|
+
);
|
package/src/cli/theme.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Theme Configuration
|
|
3
|
+
*
|
|
4
|
+
* Synthwave-inspired color theme for bargs help output, matching the retro
|
|
5
|
+
* aesthetic used throughout modestbench reporters.
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { ansi } from '@boneskull/bargs';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Synthwave-inspired theme for CLI help output
|
|
14
|
+
*
|
|
15
|
+
* Matches the retro aesthetic used in modestbench reporters
|
|
16
|
+
*/
|
|
17
|
+
export const synthwaveTheme = {
|
|
18
|
+
colors: {
|
|
19
|
+
command: ansi.brightMagenta,
|
|
20
|
+
defaultText: ansi.dim,
|
|
21
|
+
defaultValue: ansi.brightYellow,
|
|
22
|
+
description: ansi.brightWhite,
|
|
23
|
+
epilog: ansi.brightWhite,
|
|
24
|
+
example: ansi.cyan,
|
|
25
|
+
flag: ansi.brightCyan,
|
|
26
|
+
positional: ansi.brightMagenta,
|
|
27
|
+
scriptName: ansi.brightCyan + ansi.bold,
|
|
28
|
+
sectionHeader: ansi.magenta + ansi.bold,
|
|
29
|
+
type: ansi.brightWhite + ansi.dim,
|
|
30
|
+
url: ansi.brightCyan + ansi.underline,
|
|
31
|
+
usage: ansi.white,
|
|
32
|
+
},
|
|
33
|
+
};
|
package/src/errors/base.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { SITE_URL } from '../constants.js';
|
|
9
|
+
import { isError } from '../utils/type-guards.js';
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Base URL for error documentation
|
|
@@ -150,13 +151,3 @@ export const isModestBenchError = (
|
|
|
150
151
|
(error as { code: string }).code.startsWith('ERR_MB_')
|
|
151
152
|
);
|
|
152
153
|
};
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Type guard to check if an error is a standard Error
|
|
156
|
-
*
|
|
157
|
-
* @param error - The error to check
|
|
158
|
-
* @returns `true` if the error is an `Error`
|
|
159
|
-
*/
|
|
160
|
-
export const isError = (error: unknown): error is Error => {
|
|
161
|
-
return error instanceof Error;
|
|
162
|
-
};
|
|
@@ -26,17 +26,32 @@ interface RunOptions {
|
|
|
26
26
|
timeout?: number;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Node.js major version number
|
|
31
|
+
*/
|
|
32
|
+
const nodeMajorVersion = Number(process.versions.node.split('.')[0]);
|
|
33
|
+
|
|
29
34
|
/**
|
|
30
35
|
* Run a command with Node.js profiling enabled
|
|
31
36
|
*
|
|
32
37
|
* @param command - Command to run (e.g., "npm test")
|
|
33
38
|
* @param options - Execution options
|
|
34
39
|
* @returns Path to generated *.cpuprofile file
|
|
40
|
+
* @throws Error if running on Node.js 20 (--cpu-prof not allowed in
|
|
41
|
+
* NODE_OPTIONS)
|
|
35
42
|
*/
|
|
36
43
|
export const runWithProfiling = async (
|
|
37
44
|
command: string,
|
|
38
45
|
options: RunOptions = {},
|
|
39
46
|
): Promise<string> => {
|
|
47
|
+
// Node.js 20 blocks --cpu-prof in NODE_OPTIONS for security reasons
|
|
48
|
+
if (nodeMajorVersion === 20) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
'CPU profiling requires Node.js 22 or later. ' +
|
|
51
|
+
'Node.js 20 blocks --cpu-prof in NODE_OPTIONS for security reasons.',
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
40
55
|
const cwd = options.cwd || process.cwd();
|
|
41
56
|
|
|
42
57
|
// Create profiles directory
|