Skip to content

Commit 4ab7fe3

Browse files
boneskullclaude
andcommitted
chore(cli): split index.ts into focused modules
Reorganize the CLI codebase from a single 1118-line file into 13 focused modules: - index.ts: Thin entry point (115 lines) - builder.ts: Command registration with bargs - context.ts: CliContext type and dependency injection - handlers.ts: Signal handlers for graceful shutdown - theme.ts: Synthwave color theme - parsers/: Directory for all bargs parser definitions - global.ts, run.ts, init.ts, analyze.ts, test.ts - history.ts, baseline.ts (subcommand parsers) - index.ts (re-exports) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent d27dc6c commit 4ab7fe3

File tree

13 files changed

+1195
-1012
lines changed

13 files changed

+1195
-1012
lines changed

src/cli/builder.ts

Lines changed: 387 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,387 @@
1+
/**
2+
* CLI Builder
3+
*
4+
* Constructs the CLI using bargs, registering all commands, subcommands, and
5+
* their handlers.
6+
*
7+
* @packageDocumentation
8+
*/
9+
10+
import { bargs } from '@boneskull/bargs';
11+
12+
import type { CliContext } from './context.js';
13+
14+
import {
15+
handleAnalyzeCommand as analyzeCommand,
16+
type AnalyzeOptions,
17+
} from './commands/analyze.js';
18+
import {
19+
handleAnalyzeCommand as handleBaselineAnalyzeCommand,
20+
handleDeleteCommand as handleBaselineDeleteCommand,
21+
handleListCommand as handleBaselineListCommand,
22+
handleSetCommand as handleBaselineSetCommand,
23+
handleShowCommand as handleBaselineShowCommand,
24+
} from './commands/baseline.js';
25+
import {
26+
handleCleanCommand,
27+
handleCompareCommand,
28+
handleExportCommand,
29+
handleListCommand,
30+
handleShowCommand,
31+
handleTrendsCommand,
32+
} from './commands/history.js';
33+
import { handleInitCommand as initCommand } from './commands/init.js';
34+
import { handleRunCommand as runCommand } from './commands/run.js';
35+
import {
36+
handleTestCommand as testCommand,
37+
type TestOptions,
38+
} from './commands/test.js';
39+
import { createCliContext } from './context.js';
40+
import {
41+
analyzeParser,
42+
baselineAnalyzeParser,
43+
baselineDeleteParser,
44+
baselineListParser,
45+
baselineSetParser,
46+
baselineShowParser,
47+
globalOptions,
48+
historyCleanParser,
49+
historyCompareParser,
50+
historyExportParser,
51+
historyListParser,
52+
historyShowParser,
53+
historyTrendsParser,
54+
initParser,
55+
quietOption,
56+
runParser,
57+
testParser,
58+
} from './parsers/index.js';
59+
import { synthwaveTheme } from './theme.js';
60+
61+
/**
62+
* Create the CLI builder with all commands registered
63+
*
64+
* @param abortController - Controller for aborting benchmark runs
65+
* @returns Configured bargs CLI builder
66+
*/
67+
export const createCli = (abortController: AbortController) => {
68+
return bargs('modestbench', {
69+
description: 'A modern benchmark runner for Node.js',
70+
theme: synthwaveTheme,
71+
})
72+
.globals(globalOptions)
73+
.command(
74+
'run',
75+
runParser,
76+
async ({ positionals, values }) => {
77+
const [pattern] = positionals;
78+
const context = await createCliContext(
79+
values,
80+
abortController,
81+
values.engine,
82+
);
83+
const exitCode = await runCommand(context, {
84+
bail: values.bail,
85+
config: values.config,
86+
cwd: values.cwd,
87+
engine: values.engine,
88+
exclude: values.exclude,
89+
excludeTags: values['exclude-tag'],
90+
iterations: values.iterations,
91+
json: values.json,
92+
jsonPretty: values['json-pretty'],
93+
noColor: values['no-color'],
94+
outputDir: values.output,
95+
outputFile: values['output-file'],
96+
pattern,
97+
progress: values.progress,
98+
quiet: values.quiet,
99+
reporters: values.reporter,
100+
tags: values.tag,
101+
time: values.time,
102+
timeout: values.timeout,
103+
verbose: values.verbose,
104+
warmup: values.warmup,
105+
});
106+
process.exit(exitCode);
107+
},
108+
'Run benchmark files',
109+
)
110+
.command(
111+
'history',
112+
(history) =>
113+
history
114+
.globals(quietOption)
115+
.command(
116+
'list',
117+
historyListParser,
118+
async ({ values }) => {
119+
const context = await createCliContext(values, abortController);
120+
const exitCode = await handleListCommand(context, {
121+
cwd: values.cwd,
122+
format: values.format,
123+
limit: values.limit,
124+
pattern: values.pattern,
125+
since: values.since,
126+
tags: values.tag,
127+
until: values.until,
128+
verbose: values.verbose,
129+
});
130+
process.exit(exitCode);
131+
},
132+
'List recent benchmark runs',
133+
)
134+
.command(
135+
'show',
136+
historyShowParser,
137+
async ({ positionals, values }) => {
138+
const [runId] = positionals;
139+
const context = await createCliContext(values, abortController);
140+
const exitCode = await handleShowCommand(context, {
141+
cwd: values.cwd,
142+
format: values.format,
143+
runId,
144+
verbose: values.verbose,
145+
});
146+
process.exit(exitCode);
147+
},
148+
'Show detailed results for a specific run',
149+
)
150+
.command(
151+
'compare',
152+
historyCompareParser,
153+
async ({ positionals, values }) => {
154+
const [runId1, runId2] = positionals;
155+
const context = await createCliContext(values, abortController);
156+
const exitCode = await handleCompareCommand(context, {
157+
cwd: values.cwd,
158+
format: values.format,
159+
runId1,
160+
runId2,
161+
verbose: values.verbose,
162+
});
163+
process.exit(exitCode);
164+
},
165+
'Compare two benchmark runs',
166+
)
167+
.command(
168+
'trends',
169+
historyTrendsParser,
170+
async ({ positionals, values }) => {
171+
const [pattern] = positionals;
172+
const context = await createCliContext(values, abortController);
173+
const exitCode = await handleTrendsCommand(context, {
174+
all: values.all,
175+
cwd: values.cwd,
176+
format: values.format,
177+
limit: values.limit,
178+
pattern,
179+
since: values.since,
180+
tags: values.tag,
181+
until: values.until,
182+
verbose: values.verbose,
183+
});
184+
process.exit(exitCode);
185+
},
186+
'Show performance trends over time',
187+
)
188+
.command(
189+
'clean',
190+
historyCleanParser,
191+
async ({ values }) => {
192+
const context = await createCliContext(values, abortController);
193+
const exitCode = await handleCleanCommand(context, {
194+
confirm: values.yes,
195+
cwd: values.cwd,
196+
maxAge: values['max-age'],
197+
maxRuns: values['max-runs'],
198+
maxSize: values['max-size'],
199+
quiet: values.quiet,
200+
verbose: values.verbose,
201+
});
202+
process.exit(exitCode);
203+
},
204+
'Clean up old benchmark history',
205+
)
206+
.command(
207+
'export',
208+
historyExportParser,
209+
async ({ values }) => {
210+
const context = await createCliContext(values, abortController);
211+
const exitCode = await handleExportCommand(context, {
212+
cwd: values.cwd,
213+
format: values.format,
214+
outputPath: values.output,
215+
quiet: Boolean(values.quiet),
216+
since: values.since,
217+
until: values.until,
218+
verbose: values.verbose,
219+
});
220+
process.exitCode = exitCode;
221+
},
222+
'Export benchmark history to a file',
223+
),
224+
'View and manage benchmark history',
225+
)
226+
.command(
227+
'baseline',
228+
(baseline) =>
229+
baseline
230+
.globals(quietOption)
231+
.command(
232+
'set',
233+
baselineSetParser,
234+
async ({ positionals, values }) => {
235+
const [name] = positionals;
236+
const context = await createCliContext(values, abortController);
237+
const exitCode = await handleBaselineSetCommand(context, {
238+
branch: values.branch,
239+
commit: values.commit,
240+
cwd: values.cwd,
241+
default: values.default,
242+
name,
243+
quiet: Boolean(values.quiet),
244+
runId: values['run-id'],
245+
verbose: values.verbose,
246+
});
247+
process.exit(exitCode);
248+
},
249+
'Save a benchmark run as a baseline',
250+
)
251+
.command(
252+
'list',
253+
baselineListParser,
254+
async ({ values }) => {
255+
const context = await createCliContext(values, abortController);
256+
const exitCode = await handleBaselineListCommand(context, {
257+
cwd: values.cwd,
258+
format: values.format,
259+
quiet: Boolean(values.quiet),
260+
verbose: values.verbose,
261+
});
262+
process.exit(exitCode);
263+
},
264+
'List all saved baselines',
265+
)
266+
.command(
267+
'show',
268+
baselineShowParser,
269+
async ({ positionals, values }) => {
270+
const [name] = positionals;
271+
const context = await createCliContext(values, abortController);
272+
const exitCode = await handleBaselineShowCommand(context, {
273+
cwd: values.cwd,
274+
format: values.format,
275+
name,
276+
quiet: Boolean(values.quiet),
277+
verbose: values.verbose,
278+
});
279+
process.exit(exitCode);
280+
},
281+
'Show baseline details',
282+
)
283+
.command(
284+
'delete',
285+
baselineDeleteParser,
286+
async ({ positionals, values }) => {
287+
const [name] = positionals;
288+
const context = await createCliContext(values, abortController);
289+
const exitCode = await handleBaselineDeleteCommand(context, {
290+
cwd: values.cwd,
291+
name,
292+
quiet: Boolean(values.quiet),
293+
verbose: values.verbose,
294+
});
295+
process.exit(exitCode);
296+
},
297+
'Delete a baseline',
298+
)
299+
.command(
300+
'analyze',
301+
baselineAnalyzeParser,
302+
async ({ values }) => {
303+
const context = await createCliContext(values, abortController);
304+
const exitCode = await handleBaselineAnalyzeCommand(context, {
305+
confidence: values.confidence,
306+
cwd: values.cwd,
307+
quiet: Boolean(values.quiet),
308+
runs: values.runs,
309+
verbose: values.verbose,
310+
});
311+
process.exit(exitCode);
312+
},
313+
'Analyze history and suggest performance budgets',
314+
),
315+
'Manage performance baselines',
316+
)
317+
.command(
318+
'init',
319+
initParser,
320+
async ({ positionals, values }) => {
321+
const [type] = positionals;
322+
const context = await createCliContext(values, abortController);
323+
const exitCode = await initCommand(context, {
324+
configType: values['config-type'],
325+
cwd: values.cwd,
326+
examples: values.examples,
327+
force: values.force,
328+
quiet: values.quiet,
329+
type,
330+
verbose: values.verbose,
331+
yes: values.yes,
332+
});
333+
process.exitCode = exitCode;
334+
},
335+
'Initialize a new benchmark project',
336+
)
337+
.command(
338+
'analyze',
339+
analyzeParser,
340+
async ({ positionals, values }) => {
341+
const [command] = positionals;
342+
// Context not needed for analyze command currently
343+
const context = {} as CliContext;
344+
345+
const options: AnalyzeOptions = {
346+
color: !values['no-color'],
347+
command,
348+
cwd: values.cwd || process.cwd(),
349+
filterFile: values['filter-file'],
350+
groupByFile: values['group-by-file'],
351+
input: values.input,
352+
minPercent: values['min-percent'],
353+
top: values.top,
354+
};
355+
356+
process.exitCode = await analyzeCommand(context, options);
357+
},
358+
{
359+
aliases: ['profile'],
360+
description: 'Analyze code execution and identify benchmark candidates',
361+
},
362+
)
363+
.command(
364+
'test',
365+
testParser,
366+
async ({ positionals, values }) => {
367+
const [framework, files] = positionals;
368+
const context = await createCliContext(values, abortController);
369+
const options: TestOptions = {
370+
bail: values.bail,
371+
cwd: values.cwd,
372+
framework,
373+
iterations: values.iterations,
374+
json: values.json,
375+
noColor: values['no-color'],
376+
pattern: files,
377+
quiet: values.quiet,
378+
verbose: values.verbose,
379+
warmup: values.warmup,
380+
};
381+
const exitCode = await testCommand(context, options);
382+
process.exit(exitCode);
383+
},
384+
'Run test files as benchmarks',
385+
)
386+
.defaultCommand('run');
387+
};

0 commit comments

Comments
 (0)