argsbarg 1.4.0 → 1.4.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/src/validate.ts CHANGED
@@ -12,6 +12,7 @@ import {
12
12
  CliOptionKind,
13
13
  CliSchemaValidationError,
14
14
  } from "./types.ts";
15
+ import { MCP_SCHEMA_URI_DEFAULT } from "./mcp/tools.ts";
15
16
 
16
17
  const reservedCommandNames = ["completion", "mcp"];
17
18
 
@@ -34,22 +35,6 @@ export function cliValidateRoot(root: CliCommand): void {
34
35
 
35
36
  /** Recursively validates a command node: handlers vs children, options, and positionals. */
36
37
  function walkCommand(cmd: CliCommand, isRoot: boolean = false): void {
37
- // Fallback only on root
38
- if (!isRoot && cmd.fallbackCommand !== undefined) {
39
- throw new CliSchemaValidationError(
40
- "Fallback is only supported on the program root (not on " + cmd.key + ")",
41
- );
42
- }
43
- if (
44
- !isRoot &&
45
- cmd.fallbackMode !== undefined &&
46
- cmd.fallbackMode !== CliFallbackMode.MissingOnly
47
- ) {
48
- throw new CliSchemaValidationError(
49
- "fallbackMode may only be set on the program root (not on " + cmd.key + ")",
50
- );
51
- }
52
-
53
38
  if (!isRoot && cmd.mcpServer !== undefined) {
54
39
  throw new CliSchemaValidationError(
55
40
  "mcpServer is only supported on the program root (not on " + cmd.key + ")",
@@ -66,6 +51,19 @@ function walkCommand(cmd: CliCommand, isRoot: boolean = false): void {
66
51
  throw new CliSchemaValidationError("mcpTool is only supported on leaf commands");
67
52
  }
68
53
 
54
+ if (isRoot && cmd.mcpServer?.resources) {
55
+ const schemaUri = cmd.mcpServer.schemaResourceUri ?? MCP_SCHEMA_URI_DEFAULT;
56
+ const uris = cmd.mcpServer.resources.map((r) => r.uri);
57
+ if (uris.includes(schemaUri)) {
58
+ throw new CliSchemaValidationError(
59
+ `mcpServer.resources URI '${schemaUri}' conflicts with the built-in schema resource`,
60
+ );
61
+ }
62
+ if (new Set(uris).size !== uris.length) {
63
+ throw new CliSchemaValidationError("mcpServer.resources URIs must be unique");
64
+ }
65
+ }
66
+
69
67
  // Check for duplicate child names
70
68
  const seenNames = new Set<string>();
71
69
  for (const child of cmd.commands ?? []) {
@@ -75,6 +73,22 @@ function walkCommand(cmd: CliCommand, isRoot: boolean = false): void {
75
73
  seenNames.add(child.key);
76
74
  }
77
75
 
76
+ if (cmd.fallbackMode !== undefined && cmd.fallbackCommand === undefined) {
77
+ throw new CliSchemaValidationError(
78
+ `fallbackMode requires fallbackCommand on '${cmd.key}'`,
79
+ );
80
+ }
81
+
82
+ if (cmd.fallbackCommand !== undefined) {
83
+ const children = cmd.commands ?? [];
84
+ const valid = children.find((c) => c.key === cmd.fallbackCommand);
85
+ if (!valid) {
86
+ throw new CliSchemaValidationError(
87
+ `fallbackCommand '${cmd.fallbackCommand}' is not a child of '${cmd.key}'`,
88
+ );
89
+ }
90
+ }
91
+
78
92
  // Validate options (short name uniqueness, reserved -h, required presence)
79
93
  const seenShorts = new Set<string>();
80
94
  for (const opt of cmd.options ?? []) {
@@ -103,6 +117,30 @@ function walkCommand(cmd: CliCommand, isRoot: boolean = false): void {
103
117
  }
104
118
  seenShorts.add(opt.shortName);
105
119
  }
120
+
121
+ if (opt.kind === CliOptionKind.Enum) {
122
+ if (!opt.choices || opt.choices.length === 0) {
123
+ throw new CliSchemaValidationError(
124
+ `Option '${opt.name}' on '${cmd.key}': Enum kind requires non-empty choices`,
125
+ );
126
+ }
127
+ if (new Set(opt.choices).size !== opt.choices.length) {
128
+ throw new CliSchemaValidationError(
129
+ `Option '${opt.name}' on '${cmd.key}': Enum choices must be distinct`,
130
+ );
131
+ }
132
+ for (const choice of opt.choices) {
133
+ if (choice.length === 0) {
134
+ throw new CliSchemaValidationError(
135
+ `Option '${opt.name}' on '${cmd.key}': Enum choices must be non-empty strings`,
136
+ );
137
+ }
138
+ }
139
+ } else if (opt.choices !== undefined) {
140
+ throw new CliSchemaValidationError(
141
+ `Option '${opt.name}' on '${cmd.key}': choices is only valid for Enum kind`,
142
+ );
143
+ }
106
144
  }
107
145
 
108
146
  // Validate positionals