|
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'; |
@@ -631,24 +631,38 @@ export function parseArguments( |
631 | 631 | console.error(`Unknown arguments: ${unknownArgs.map(arg => `--${arg}`)}`); |
632 | 632 | } |
633 | 633 |
|
634 | | - // Step 5: Validation. Conflicts are only checked against explicit user input |
635 | | - // so that dynamic defaults never conflict. |
636 | | - const activeArgs = new Set<string>(); |
637 | | - for (const [key, val] of Object.entries(explicitConfig)) { |
638 | | - if (val !== undefined && val !== false) { |
639 | | - activeArgs.add(key); |
640 | | - } |
641 | | - } |
642 | | - for (const group of CONFLICTING_ARGS) { |
643 | | - const activeInGroup = group.filter(arg => activeArgs.has(arg)); |
644 | | - if (activeInGroup.length > 1) { |
645 | | - const [arg1, arg2] = activeInGroup; |
646 | | - throw new Error(`Arguments ${arg1} and ${arg2} are mutually exclusive`); |
647 | | - } |
648 | | - } |
| 634 | + // Step 5: Final Schema Validation & Type Safety. Conflicts are only checked |
| 635 | + // against explicit user input so that dynamic defaults never conflict. |
| 636 | + const ConfigSchema = z |
| 637 | + .object({}) |
| 638 | + .passthrough() |
| 639 | + .superRefine((_config, ctx) => { |
| 640 | + const activeArgs = new Set<string>(); |
| 641 | + for (const [key, val] of Object.entries(explicitConfig)) { |
| 642 | + if (val !== undefined && val !== false) { |
| 643 | + activeArgs.add(key); |
| 644 | + } |
| 645 | + } |
| 646 | + |
| 647 | + for (const group of CONFLICTING_ARGS) { |
| 648 | + const activeInGroup = group.filter(arg => activeArgs.has(arg)); |
| 649 | + if (activeInGroup.length > 1) { |
| 650 | + const [arg1, arg2] = activeInGroup; |
| 651 | + ctx.addIssue({ |
| 652 | + code: z.ZodIssueCode.custom, |
| 653 | + message: `Arguments ${arg1} and ${arg2} are mutually exclusive`, |
| 654 | + path: [arg1, arg2], |
| 655 | + }); |
| 656 | + } |
| 657 | + } |
| 658 | + }); |
649 | 659 |
|
| 660 | + const result = ConfigSchema.safeParse(resolvedConfig); |
| 661 | + if (!result.success) { |
| 662 | + throw new Error(result.error.issues[0].message); |
| 663 | + } |
650 | 664 | // The merge and the default loop lose the static type that yargs infers from |
651 | 665 | // `mcpOptions`. Every value was produced by the same option definitions (CLI |
652 | 666 | // parser, strict config-file parser, option defaults), so the shape matches. |
653 | | - return resolvedConfig as ParsedArguments; |
| 667 | + return result.data as ParsedArguments; |
654 | 668 | } |
0 commit comments