Skip to content

Commit 4551544

Browse files
committed
feat: config file schema and validation
1 parent f5813ec commit 4551544

File tree

18 files changed

+364
-193
lines changed

18 files changed

+364
-193
lines changed

package-lock.json

Lines changed: 19 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,9 @@
4848
"testing"
4949
],
5050
"scripts": {
51-
"build": "zshy",
51+
"build": "run-s -sl build:compile build:schema",
52+
"build:compile": "zshy",
53+
"build:schema": "node --import tsx scripts/generate-schema.ts",
5254
"clean": "rm -rf dist",
5355
"commitlint": "commitlint",
5456
"dev": "zshy --watch",
@@ -74,7 +76,8 @@
7476
"cosmiconfig-typescript-loader": "^6.2.0",
7577
"glob": "^11.0.3",
7678
"tinybench": "^5.0.1",
77-
"yargs": "^17.7.2"
79+
"yargs": "^17.7.2",
80+
"zod": "^4.1.12"
7881
},
7982
"devDependencies": {
8083
"@commitlint/cli": "20.1.0",
@@ -126,6 +129,7 @@
126129
],
127130
"node": {
128131
"entry": [
132+
"scripts/**/*.ts",
129133
"test/**/*.test.ts",
130134
"src/index.ts",
131135
"src/cli/index.ts",

scripts/generate-schema.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#!/usr/bin/env node
2+
3+
/**
4+
* Generate JSON Schema from Zod schemas
5+
*
6+
* This script converts the ModestBench Zod configuration schema to JSON Schema
7+
* format, enabling IDE autocomplete and validation in config files.
8+
*/
9+
10+
import { mkdir, writeFile } from 'node:fs/promises';
11+
import { dirname, resolve } from 'node:path';
12+
import { fileURLToPath } from 'node:url';
13+
import * as z from 'zod';
14+
15+
import { partialModestBenchConfigSchema } from '../src/config/schema.js';
16+
17+
const __filename = fileURLToPath(import.meta.url);
18+
const __dirname = dirname(__filename);
19+
20+
const generateSchema = async () => {
21+
try {
22+
// Convert Zod schema to JSON Schema using native Zod v4 functionality
23+
const jsonSchema = z.toJSONSchema(partialModestBenchConfigSchema, {
24+
target: 'draft-2020-12',
25+
});
26+
27+
// Add top-level schema metadata
28+
const schemaWithMetadata = {
29+
$id: 'https://github.com/boneskull/modestbench/schema/config.json',
30+
$schema: 'https://json-schema.org/draft/2020-12/schema',
31+
...jsonSchema,
32+
};
33+
34+
// Ensure output directory exists
35+
const outputPath = resolve(__dirname, '../dist/schema');
36+
await mkdir(outputPath, { recursive: true });
37+
38+
// Write the JSON Schema to file with pretty printing
39+
const outputFile = resolve(outputPath, 'modestbench-config.schema.json');
40+
await writeFile(
41+
outputFile,
42+
JSON.stringify(schemaWithMetadata, null, 2) + '\n',
43+
'utf-8',
44+
);
45+
46+
console.log(`✓ Generated JSON Schema: ${outputFile}`);
47+
} catch (error) {
48+
console.error('Failed to generate JSON Schema:', error);
49+
process.exit(1);
50+
}
51+
};
52+
53+
// Run the generator
54+
void generateSchema();

src/cli/commands/history.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ interface HistoryOptions {
2020
maxAge?: number | undefined;
2121
maxRuns?: number | undefined;
2222
maxSize?: number | undefined;
23-
output?: string | undefined;
23+
outputDir?: string | undefined;
2424
pattern?: string | undefined;
2525
quiet?: boolean | undefined;
2626
since?: string | undefined;
@@ -252,12 +252,12 @@ const handleExportCommand = async (
252252
query,
253253
);
254254

255-
if (options.output) {
255+
if (options.outputDir) {
256256
// Write to file
257257
const fs = await import('node:fs/promises');
258-
await fs.writeFile(options.output, exportData, 'utf8');
258+
await fs.writeFile(options.outputDir, exportData, 'utf8');
259259
if (!options.quiet) {
260-
console.log(`Exported history to ${options.output}`);
260+
console.log(`Exported history to ${options.outputDir}`);
261261
}
262262
} else {
263263
// Write to stdout

src/cli/commands/run.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ interface RunOptions {
2323
iterations?: number | undefined;
2424
json?: boolean | undefined;
2525
noColor?: boolean | undefined;
26-
output?: string | undefined;
26+
outputDir?: string | undefined;
2727
pattern: string[];
2828
quiet?: boolean | undefined;
2929
reporters: string[];
@@ -45,7 +45,7 @@ export const handleRunCommand = async (
4545
// (i.e., outputting to stdout where we need clean JSON)
4646
const isUsingJsonReporter = options.reporters?.includes('json') ?? false;
4747
const shouldBeQuiet =
48-
options.quiet || (isUsingJsonReporter && !options.output);
48+
options.quiet || (isUsingJsonReporter && !options.outputDir);
4949

5050
try {
5151
// Step 1: Load and merge configuration
@@ -62,7 +62,7 @@ export const handleRunCommand = async (
6262
context,
6363
config,
6464
shouldBeQuiet,
65-
options.output,
65+
options.outputDir,
6666
);
6767

6868
// Step 3: Discovery phase
@@ -208,8 +208,8 @@ const loadConfiguration = async (context: CliContext, options: RunOptions) => {
208208
if (options.reporters) {
209209
cliArgs.reporters = options.reporters;
210210
}
211-
if (options.output) {
212-
cliArgs.outputDir = resolve(options.cwd, options.output);
211+
if (options.outputDir) {
212+
cliArgs.outputDir = resolve(options.cwd, options.outputDir);
213213
}
214214
if (options.iterations) {
215215
cliArgs.iterations = options.iterations;

src/cli/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ export const main = async (
255255
iterations: argv.iterations,
256256
json: argv.json,
257257
noColor: argv.noColor,
258-
output: argv.output,
258+
outputDir: argv.output,
259259
pattern: argv.pattern,
260260
quiet: argv.quiet,
261261
reporters: argv.reporters,
@@ -360,7 +360,7 @@ export const main = async (
360360
maxAge: argv.maxAge,
361361
maxRuns: argv.maxRuns,
362362
maxSize: argv.maxSize,
363-
output: argv.output,
363+
outputDir: argv.output,
364364
pattern: argv.pattern,
365365
quiet: Boolean(argv.quiet),
366366
since: argv.since,

0 commit comments

Comments
 (0)