|
5 | 5 | */ |
6 | 6 |
|
7 | 7 | import type {YargsOptions} from '../third_party/index.js'; |
8 | | -import {yargs, hideBin} from '../third_party/index.js'; |
| 8 | +import {yargs, hideBin, zod as z} from '../third_party/index.js'; |
9 | 9 | import os from 'node:os'; |
10 | 10 | import {readFileSync} from 'node:fs'; |
11 | 11 | import path from 'node:path'; |
@@ -610,24 +610,38 @@ export function parseArguments( |
610 | 610 | console.error(`Unknown arguments: ${unknownArgs.map(arg => `--${arg}`)}`); |
611 | 611 | } |
612 | 612 |
|
613 | | - // Step 5: Validation. Conflicts are only checked against explicit user input |
614 | | - // so that dynamic defaults never conflict. |
615 | | - const activeArgs = new Set<string>(); |
616 | | - for (const [key, val] of Object.entries(explicitConfig)) { |
617 | | - if (val !== undefined && val !== false) { |
618 | | - activeArgs.add(key); |
619 | | - } |
620 | | - } |
621 | | - for (const group of CONFLICTING_ARGS) { |
622 | | - const activeInGroup = group.filter(arg => activeArgs.has(arg)); |
623 | | - if (activeInGroup.length > 1) { |
624 | | - const [arg1, arg2] = activeInGroup; |
625 | | - throw new Error(`Arguments ${arg1} and ${arg2} are mutually exclusive`); |
626 | | - } |
627 | | - } |
| 613 | + // Step 5: Final Schema Validation & Type Safety. Conflicts are only checked |
| 614 | + // against explicit user input so that dynamic defaults never conflict. |
| 615 | + const ConfigSchema = z |
| 616 | + .object({}) |
| 617 | + .passthrough() |
| 618 | + .superRefine((_config, ctx) => { |
| 619 | + const activeArgs = new Set<string>(); |
| 620 | + for (const [key, val] of Object.entries(explicitConfig)) { |
| 621 | + if (val !== undefined && val !== false) { |
| 622 | + activeArgs.add(key); |
| 623 | + } |
| 624 | + } |
| 625 | + |
| 626 | + for (const group of CONFLICTING_ARGS) { |
| 627 | + const activeInGroup = group.filter(arg => activeArgs.has(arg)); |
| 628 | + if (activeInGroup.length > 1) { |
| 629 | + const [arg1, arg2] = activeInGroup; |
| 630 | + ctx.addIssue({ |
| 631 | + code: z.ZodIssueCode.custom, |
| 632 | + message: `Arguments ${arg1} and ${arg2} are mutually exclusive`, |
| 633 | + path: [arg1, arg2], |
| 634 | + }); |
| 635 | + } |
| 636 | + } |
| 637 | + }); |
628 | 638 |
|
| 639 | + const result = ConfigSchema.safeParse(resolvedConfig); |
| 640 | + if (!result.success) { |
| 641 | + throw new Error(result.error.issues[0].message); |
| 642 | + } |
629 | 643 | // The merge and the default loop lose the static type that yargs infers from |
630 | 644 | // `mcpOptions`. Every value was produced by the same option definitions (CLI |
631 | 645 | // parser, strict config-file parser, option defaults), so the shape matches. |
632 | | - return resolvedConfig as ParsedArguments; |
| 646 | + return result.data as ParsedArguments; |
633 | 647 | } |
0 commit comments