@bryan-thompson/inspector-assessment-cli 1.30.1 → 1.31.0
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/build/lib/__tests__/cli-parserSchemas.test.js +553 -0
- package/build/lib/__tests__/zodErrorFormatter.test.js +282 -0
- package/build/lib/assessment-runner/__tests__/server-configSchemas.test.js +394 -0
- package/build/lib/assessment-runner/assessment-executor.js +4 -0
- package/build/lib/assessment-runner/config-builder.js +9 -0
- package/build/lib/assessment-runner/server-configSchemas.js +122 -0
- package/build/lib/cli-parser.js +23 -25
- package/build/lib/cli-parserSchemas.js +231 -0
- package/build/lib/zodErrorFormatter.js +91 -0
- package/package.json +1 -1
|
@@ -68,6 +68,15 @@ export function buildConfig(options) {
|
|
|
68
68
|
if (options.temporalInvocations) {
|
|
69
69
|
config.temporalInvocations = options.temporalInvocations;
|
|
70
70
|
}
|
|
71
|
+
// Official MCP conformance testing (opt-in via --conformance flag)
|
|
72
|
+
// Requires HTTP/SSE transport with serverUrl - STDIO transport will skip gracefully
|
|
73
|
+
if (options.conformanceEnabled) {
|
|
74
|
+
config.assessmentCategories = {
|
|
75
|
+
...config.assessmentCategories,
|
|
76
|
+
conformance: true,
|
|
77
|
+
};
|
|
78
|
+
console.log("🔍 Official MCP conformance testing enabled");
|
|
79
|
+
}
|
|
71
80
|
if (options.claudeEnabled) {
|
|
72
81
|
// Check for HTTP transport via --claude-http flag or environment variables
|
|
73
82
|
const useHttpTransport = options.claudeHttp || process.env.INSPECTOR_CLAUDE === "true";
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zod Schemas for Server Configuration Files
|
|
3
|
+
*
|
|
4
|
+
* Runtime validation for MCP server configuration loaded from JSON files.
|
|
5
|
+
* Supports both Claude Desktop config format and standalone config format.
|
|
6
|
+
*
|
|
7
|
+
* @module cli/lib/assessment-runner/server-configSchemas
|
|
8
|
+
*
|
|
9
|
+
* @remarks
|
|
10
|
+
* This module provides type-discriminated schemas for config FILE parsing,
|
|
11
|
+
* with separate schemas for each transport type (HttpSseServerConfigSchema,
|
|
12
|
+
* StdioServerConfigSchema). This enables type-safe handling of transport-specific
|
|
13
|
+
* fields.
|
|
14
|
+
*
|
|
15
|
+
* For flexible CLI argument parsing where transport may not be specified,
|
|
16
|
+
* see cli-parserSchemas.ts (ServerConfigSchema) which uses a single schema
|
|
17
|
+
* with refinement-based validation.
|
|
18
|
+
*
|
|
19
|
+
* **Design Decision (Issue #114):**
|
|
20
|
+
* Both schema approaches are kept because they serve different purposes:
|
|
21
|
+
* - server-configSchemas.ts: Type-safe discriminated unions for file parsing
|
|
22
|
+
* - cli-parserSchemas.ts: Flexible validation for CLI argument parsing
|
|
23
|
+
*
|
|
24
|
+
* @see cli-parserSchemas.ts for CLI argument validation
|
|
25
|
+
* @see sharedSchemas.ts for common schemas (TransportTypeSchema, etc.)
|
|
26
|
+
*/
|
|
27
|
+
import { z } from "zod";
|
|
28
|
+
// Import TransportTypeSchema from shared schemas for consistency
|
|
29
|
+
import { TransportTypeSchema } from "../../../../client/lib/lib/assessment/sharedSchemas.js";
|
|
30
|
+
// Re-export for backwards compatibility
|
|
31
|
+
export { TransportTypeSchema };
|
|
32
|
+
/**
|
|
33
|
+
* Schema for HTTP/SSE transport server configuration.
|
|
34
|
+
*/
|
|
35
|
+
export const HttpSseServerConfigSchema = z.object({
|
|
36
|
+
transport: z.enum(["http", "sse"]).optional(),
|
|
37
|
+
url: z.string().url("url must be a valid URL"),
|
|
38
|
+
});
|
|
39
|
+
/**
|
|
40
|
+
* Schema for stdio transport server configuration.
|
|
41
|
+
*/
|
|
42
|
+
export const StdioServerConfigSchema = z.object({
|
|
43
|
+
transport: z.literal("stdio").optional(),
|
|
44
|
+
command: z.string().min(1, "command is required for stdio transport"),
|
|
45
|
+
args: z.array(z.string()).optional().default([]),
|
|
46
|
+
env: z.record(z.string()).optional().default({}),
|
|
47
|
+
cwd: z.string().optional(),
|
|
48
|
+
});
|
|
49
|
+
/**
|
|
50
|
+
* Schema for a single server entry (either transport type).
|
|
51
|
+
* Used within mcpServers object or as standalone config.
|
|
52
|
+
*/
|
|
53
|
+
export const ServerEntrySchema = z.union([
|
|
54
|
+
HttpSseServerConfigSchema,
|
|
55
|
+
StdioServerConfigSchema,
|
|
56
|
+
]);
|
|
57
|
+
/**
|
|
58
|
+
* Schema for Claude Desktop config format.
|
|
59
|
+
* Contains nested mcpServers object with server configurations.
|
|
60
|
+
*/
|
|
61
|
+
export const ClaudeDesktopConfigSchema = z.object({
|
|
62
|
+
mcpServers: z.record(ServerEntrySchema).optional(),
|
|
63
|
+
});
|
|
64
|
+
/**
|
|
65
|
+
* Schema for standalone config file format.
|
|
66
|
+
* Direct server configuration without nesting.
|
|
67
|
+
*/
|
|
68
|
+
export const StandaloneConfigSchema = ServerEntrySchema;
|
|
69
|
+
/**
|
|
70
|
+
* Combined schema for any valid config file format.
|
|
71
|
+
*/
|
|
72
|
+
export const ConfigFileSchema = z.union([
|
|
73
|
+
ClaudeDesktopConfigSchema,
|
|
74
|
+
StandaloneConfigSchema,
|
|
75
|
+
]);
|
|
76
|
+
/**
|
|
77
|
+
* Parse a config file's JSON content and validate its structure.
|
|
78
|
+
*
|
|
79
|
+
* @param jsonContent - Raw JSON object from file
|
|
80
|
+
* @returns Validated config file content
|
|
81
|
+
* @throws ZodError if validation fails
|
|
82
|
+
*/
|
|
83
|
+
export function parseConfigFile(jsonContent) {
|
|
84
|
+
return ConfigFileSchema.parse(jsonContent);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Safely parse a config file without throwing.
|
|
88
|
+
*
|
|
89
|
+
* @param jsonContent - Raw JSON object from file
|
|
90
|
+
* @returns SafeParseResult with success status and data/error
|
|
91
|
+
*/
|
|
92
|
+
export function safeParseConfigFile(jsonContent) {
|
|
93
|
+
return ConfigFileSchema.safeParse(jsonContent);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Validate a server entry matches expected structure.
|
|
97
|
+
*
|
|
98
|
+
* @param entry - Server configuration entry
|
|
99
|
+
* @returns Array of validation error messages (empty if valid)
|
|
100
|
+
*/
|
|
101
|
+
export function validateServerEntry(entry) {
|
|
102
|
+
const result = ServerEntrySchema.safeParse(entry);
|
|
103
|
+
if (result.success) {
|
|
104
|
+
return [];
|
|
105
|
+
}
|
|
106
|
+
return result.error.errors.map((e) => {
|
|
107
|
+
const path = e.path.length > 0 ? `${e.path.join(".")}: ` : "";
|
|
108
|
+
return `${path}${e.message}`;
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Check if a server entry is HTTP/SSE transport.
|
|
113
|
+
*/
|
|
114
|
+
export function isHttpSseConfig(entry) {
|
|
115
|
+
return ("url" in entry || entry.transport === "http" || entry.transport === "sse");
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Check if a server entry is stdio transport.
|
|
119
|
+
*/
|
|
120
|
+
export function isStdioConfig(entry) {
|
|
121
|
+
return "command" in entry && !("url" in entry);
|
|
122
|
+
}
|
package/build/lib/cli-parser.js
CHANGED
|
@@ -9,36 +9,33 @@
|
|
|
9
9
|
* @module cli/lib/cli-parser
|
|
10
10
|
*/
|
|
11
11
|
import { ASSESSMENT_CATEGORY_METADATA, } from "../../../client/lib/lib/assessmentTypes.js";
|
|
12
|
-
import { ASSESSMENT_PROFILES,
|
|
12
|
+
import { ASSESSMENT_PROFILES, getProfileHelpText, TIER_1_CORE_SECURITY, TIER_2_COMPLIANCE, TIER_3_CAPABILITY, TIER_4_EXTENDED, } from "../profiles.js";
|
|
13
13
|
import packageJson from "../../package.json" with { type: "json" };
|
|
14
|
+
import { safeParseModuleNames, LogLevelSchema, ReportFormatSchema, AssessmentProfileNameSchema, } from "./cli-parserSchemas.js";
|
|
14
15
|
// ============================================================================
|
|
15
16
|
// Constants
|
|
16
17
|
// ============================================================================
|
|
17
|
-
// Valid module names derived from ASSESSMENT_CATEGORY_METADATA
|
|
18
|
+
// Valid module names derived from ASSESSMENT_CATEGORY_METADATA (used for help text)
|
|
18
19
|
const VALID_MODULE_NAMES = Object.keys(ASSESSMENT_CATEGORY_METADATA);
|
|
19
20
|
// ============================================================================
|
|
20
21
|
// Validation Functions
|
|
21
22
|
// ============================================================================
|
|
22
23
|
/**
|
|
23
|
-
* Validate module names from CLI input
|
|
24
|
+
* Validate module names from CLI input using Zod schema.
|
|
24
25
|
*
|
|
25
26
|
* @param input - Comma-separated module names
|
|
26
27
|
* @param flagName - Flag name for error messages (e.g., "--skip-modules")
|
|
27
28
|
* @returns Array of validated module names, or empty array if invalid
|
|
28
29
|
*/
|
|
29
30
|
export function validateModuleNames(input, flagName) {
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
.
|
|
33
|
-
.filter(Boolean);
|
|
34
|
-
const invalid = names.filter((n) => !VALID_MODULE_NAMES.includes(n));
|
|
35
|
-
if (invalid.length > 0) {
|
|
36
|
-
console.error(`Error: Invalid module name(s) for ${flagName}: ${invalid.join(", ")}`);
|
|
31
|
+
const result = safeParseModuleNames(input);
|
|
32
|
+
if (result.invalid.length > 0) {
|
|
33
|
+
console.error(`Error: Invalid module name(s) for ${flagName}: ${result.invalid.join(", ")}`);
|
|
37
34
|
console.error(`Valid modules: ${VALID_MODULE_NAMES.join(", ")}`);
|
|
38
35
|
setTimeout(() => process.exit(1), 10);
|
|
39
36
|
return [];
|
|
40
37
|
}
|
|
41
|
-
return
|
|
38
|
+
return result.valid;
|
|
42
39
|
}
|
|
43
40
|
/**
|
|
44
41
|
* Validate parsed options for consistency and requirements
|
|
@@ -146,20 +143,14 @@ export function parseArgs(argv) {
|
|
|
146
143
|
break;
|
|
147
144
|
case "--log-level": {
|
|
148
145
|
const levelValue = args[++i];
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
"warn",
|
|
153
|
-
"info",
|
|
154
|
-
"debug",
|
|
155
|
-
];
|
|
156
|
-
if (!validLevels.includes(levelValue)) {
|
|
157
|
-
console.error(`Invalid log level: ${levelValue}. Valid options: ${validLevels.join(", ")}`);
|
|
146
|
+
const parseResult = LogLevelSchema.safeParse(levelValue);
|
|
147
|
+
if (!parseResult.success) {
|
|
148
|
+
console.error(`Invalid log level: ${levelValue}. Valid options: silent, error, warn, info, debug`);
|
|
158
149
|
setTimeout(() => process.exit(1), 10);
|
|
159
150
|
options.helpRequested = true;
|
|
160
151
|
return options;
|
|
161
152
|
}
|
|
162
|
-
options.logLevel =
|
|
153
|
+
options.logLevel = parseResult.data;
|
|
163
154
|
break;
|
|
164
155
|
}
|
|
165
156
|
case "--json":
|
|
@@ -168,13 +159,14 @@ export function parseArgs(argv) {
|
|
|
168
159
|
case "--format":
|
|
169
160
|
case "-f": {
|
|
170
161
|
const formatValue = args[++i];
|
|
171
|
-
|
|
162
|
+
const parseResult = ReportFormatSchema.safeParse(formatValue);
|
|
163
|
+
if (!parseResult.success) {
|
|
172
164
|
console.error(`Invalid format: ${formatValue}. Valid options: json, markdown`);
|
|
173
165
|
setTimeout(() => process.exit(1), 10);
|
|
174
166
|
options.helpRequested = true;
|
|
175
167
|
return options;
|
|
176
168
|
}
|
|
177
|
-
options.format =
|
|
169
|
+
options.format = parseResult.data;
|
|
178
170
|
break;
|
|
179
171
|
}
|
|
180
172
|
case "--include-policy":
|
|
@@ -201,6 +193,10 @@ export function parseArgs(argv) {
|
|
|
201
193
|
case "--skip-temporal":
|
|
202
194
|
options.skipTemporal = true;
|
|
203
195
|
break;
|
|
196
|
+
case "--conformance":
|
|
197
|
+
// Enable official MCP conformance tests (requires HTTP/SSE transport with serverUrl)
|
|
198
|
+
options.conformanceEnabled = true;
|
|
199
|
+
break;
|
|
204
200
|
case "--profile": {
|
|
205
201
|
const profileValue = args[++i];
|
|
206
202
|
if (!profileValue) {
|
|
@@ -210,14 +206,15 @@ export function parseArgs(argv) {
|
|
|
210
206
|
options.helpRequested = true;
|
|
211
207
|
return options;
|
|
212
208
|
}
|
|
213
|
-
|
|
209
|
+
const parseResult = AssessmentProfileNameSchema.safeParse(profileValue);
|
|
210
|
+
if (!parseResult.success) {
|
|
214
211
|
console.error(`Error: Invalid profile name: ${profileValue}`);
|
|
215
212
|
console.error(`Valid profiles: ${Object.keys(ASSESSMENT_PROFILES).join(", ")}`);
|
|
216
213
|
setTimeout(() => process.exit(1), 10);
|
|
217
214
|
options.helpRequested = true;
|
|
218
215
|
return options;
|
|
219
216
|
}
|
|
220
|
-
options.profile =
|
|
217
|
+
options.profile = parseResult.data;
|
|
221
218
|
break;
|
|
222
219
|
}
|
|
223
220
|
case "--skip-modules": {
|
|
@@ -364,6 +361,7 @@ Options:
|
|
|
364
361
|
--profile <name> Use predefined module profile (quick, security, compliance, full)
|
|
365
362
|
--temporal-invocations <n> Number of invocations per tool for rug pull detection (default: 25)
|
|
366
363
|
--skip-temporal Skip temporal/rug pull testing (faster assessment)
|
|
364
|
+
--conformance Enable official MCP conformance tests (experimental, requires HTTP/SSE transport)
|
|
367
365
|
--skip-modules <list> Skip specific modules (comma-separated)
|
|
368
366
|
--only-modules <list> Run only specific modules (comma-separated)
|
|
369
367
|
--json Output only JSON path (no console summary)
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zod Schemas for CLI Argument Parsing
|
|
3
|
+
*
|
|
4
|
+
* Runtime validation schemas for CLI arguments and server configuration.
|
|
5
|
+
* Replaces manual validation in parseArgs() and validateArgs() functions.
|
|
6
|
+
*
|
|
7
|
+
* @module cli/lib/cli-parserSchemas
|
|
8
|
+
*/
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
// Import shared schemas from single source of truth
|
|
11
|
+
import { LogLevelSchema, ReportFormatSchema, TransportTypeSchema, ZOD_SCHEMA_VERSION, } from "../../../client/lib/lib/assessment/sharedSchemas.js";
|
|
12
|
+
// Re-export shared schemas for backwards compatibility
|
|
13
|
+
export { LogLevelSchema, ReportFormatSchema, TransportTypeSchema };
|
|
14
|
+
// Export schema version for consumers
|
|
15
|
+
export { ZOD_SCHEMA_VERSION };
|
|
16
|
+
/**
|
|
17
|
+
* Valid assessment profile names.
|
|
18
|
+
*/
|
|
19
|
+
export const AssessmentProfileNameSchema = z.enum([
|
|
20
|
+
"quick",
|
|
21
|
+
"security",
|
|
22
|
+
"compliance",
|
|
23
|
+
"full",
|
|
24
|
+
]);
|
|
25
|
+
/**
|
|
26
|
+
* Valid assessment module names.
|
|
27
|
+
* Derived from ASSESSMENT_CATEGORY_METADATA in coreTypes.ts.
|
|
28
|
+
*/
|
|
29
|
+
export const AssessmentModuleNameSchema = z.enum([
|
|
30
|
+
"functionality",
|
|
31
|
+
"security",
|
|
32
|
+
"documentation",
|
|
33
|
+
"errorHandling",
|
|
34
|
+
"usability",
|
|
35
|
+
"mcpSpecCompliance",
|
|
36
|
+
"aupCompliance",
|
|
37
|
+
"toolAnnotations",
|
|
38
|
+
"prohibitedLibraries",
|
|
39
|
+
"manifestValidation",
|
|
40
|
+
"portability",
|
|
41
|
+
"externalAPIScanner",
|
|
42
|
+
"authentication",
|
|
43
|
+
"temporal",
|
|
44
|
+
"resources",
|
|
45
|
+
"prompts",
|
|
46
|
+
"crossCapability",
|
|
47
|
+
"protocolConformance",
|
|
48
|
+
]);
|
|
49
|
+
// ============================================================================
|
|
50
|
+
// Server Configuration Schema
|
|
51
|
+
// ============================================================================
|
|
52
|
+
/**
|
|
53
|
+
* Schema for server connection configuration.
|
|
54
|
+
* Validates transport-specific required fields.
|
|
55
|
+
*
|
|
56
|
+
* @remarks
|
|
57
|
+
* This schema provides flexible validation for CLI argument parsing.
|
|
58
|
+
* For type-safe config file parsing with discriminated unions, see
|
|
59
|
+
* server-configSchemas.ts (HttpSseServerConfigSchema, StdioServerConfigSchema).
|
|
60
|
+
*/
|
|
61
|
+
export const ServerConfigSchema = z
|
|
62
|
+
.object({
|
|
63
|
+
transport: TransportTypeSchema.optional(),
|
|
64
|
+
command: z.string().optional(),
|
|
65
|
+
args: z.array(z.string()).optional(),
|
|
66
|
+
env: z.record(z.string()).optional(),
|
|
67
|
+
cwd: z.string().optional(),
|
|
68
|
+
url: z.string().optional(),
|
|
69
|
+
})
|
|
70
|
+
.refine((data) => {
|
|
71
|
+
// For http/sse transport, url is required
|
|
72
|
+
if (data.transport === "http" || data.transport === "sse") {
|
|
73
|
+
return !!data.url;
|
|
74
|
+
}
|
|
75
|
+
// For stdio transport, command is required
|
|
76
|
+
if (data.transport === "stdio") {
|
|
77
|
+
return !!data.command;
|
|
78
|
+
}
|
|
79
|
+
// If no transport specified, either url or command must be present
|
|
80
|
+
return !!data.url || !!data.command;
|
|
81
|
+
}, {
|
|
82
|
+
message: "For http/sse transport, 'url' is required. For stdio transport, 'command' is required.",
|
|
83
|
+
});
|
|
84
|
+
// ============================================================================
|
|
85
|
+
// Assessment Options Schema
|
|
86
|
+
// ============================================================================
|
|
87
|
+
/**
|
|
88
|
+
* Schema for assessment CLI options.
|
|
89
|
+
* Validates all command-line arguments and their constraints.
|
|
90
|
+
*/
|
|
91
|
+
export const AssessmentOptionsSchema = z
|
|
92
|
+
.object({
|
|
93
|
+
serverName: z.string().min(1, "--server is required"),
|
|
94
|
+
serverConfigPath: z.string().optional(),
|
|
95
|
+
outputPath: z.string().optional(),
|
|
96
|
+
sourceCodePath: z.string().optional(),
|
|
97
|
+
patternConfigPath: z.string().optional(),
|
|
98
|
+
performanceConfigPath: z.string().optional(),
|
|
99
|
+
claudeEnabled: z.boolean().optional(),
|
|
100
|
+
claudeHttp: z.boolean().optional(),
|
|
101
|
+
mcpAuditorUrl: z
|
|
102
|
+
.string()
|
|
103
|
+
.url("--mcp-auditor-url must be a valid URL")
|
|
104
|
+
.optional(),
|
|
105
|
+
fullAssessment: z.boolean().optional(),
|
|
106
|
+
verbose: z.boolean().optional(),
|
|
107
|
+
jsonOnly: z.boolean().optional(),
|
|
108
|
+
helpRequested: z.boolean().optional(),
|
|
109
|
+
versionRequested: z.boolean().optional(),
|
|
110
|
+
format: ReportFormatSchema.optional(),
|
|
111
|
+
includePolicy: z.boolean().optional(),
|
|
112
|
+
preflightOnly: z.boolean().optional(),
|
|
113
|
+
comparePath: z.string().optional(),
|
|
114
|
+
diffOnly: z.boolean().optional(),
|
|
115
|
+
resume: z.boolean().optional(),
|
|
116
|
+
noResume: z.boolean().optional(),
|
|
117
|
+
temporalInvocations: z.number().int().positive().optional(),
|
|
118
|
+
skipTemporal: z.boolean().optional(),
|
|
119
|
+
skipModules: z.array(AssessmentModuleNameSchema).optional(),
|
|
120
|
+
onlyModules: z.array(AssessmentModuleNameSchema).optional(),
|
|
121
|
+
profile: AssessmentProfileNameSchema.optional(),
|
|
122
|
+
logLevel: LogLevelSchema.optional(),
|
|
123
|
+
listModules: z.boolean().optional(),
|
|
124
|
+
})
|
|
125
|
+
.refine((data) => !(data.profile && (data.skipModules?.length || data.onlyModules?.length)), {
|
|
126
|
+
message: "--profile cannot be used with --skip-modules or --only-modules",
|
|
127
|
+
path: ["profile"],
|
|
128
|
+
})
|
|
129
|
+
.refine((data) => !(data.skipModules?.length && data.onlyModules?.length), {
|
|
130
|
+
message: "--skip-modules and --only-modules are mutually exclusive",
|
|
131
|
+
path: ["skipModules"],
|
|
132
|
+
});
|
|
133
|
+
/**
|
|
134
|
+
* Schema for validation result.
|
|
135
|
+
*/
|
|
136
|
+
export const ValidationResultSchema = z.object({
|
|
137
|
+
valid: z.boolean(),
|
|
138
|
+
errors: z.array(z.string()),
|
|
139
|
+
});
|
|
140
|
+
// ============================================================================
|
|
141
|
+
// Validation Helpers
|
|
142
|
+
// ============================================================================
|
|
143
|
+
/**
|
|
144
|
+
* Validate assessment options and return error messages.
|
|
145
|
+
*
|
|
146
|
+
* @param options - Options to validate
|
|
147
|
+
* @returns Array of validation error messages (empty if valid)
|
|
148
|
+
*/
|
|
149
|
+
export function validateAssessmentOptions(options) {
|
|
150
|
+
const result = AssessmentOptionsSchema.safeParse(options);
|
|
151
|
+
if (result.success) {
|
|
152
|
+
return [];
|
|
153
|
+
}
|
|
154
|
+
return result.error.errors.map((e) => {
|
|
155
|
+
const path = e.path.length > 0 ? `${e.path.join(".")}: ` : "";
|
|
156
|
+
return `${path}${e.message}`;
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Validate a server configuration.
|
|
161
|
+
*
|
|
162
|
+
* @param config - Server config to validate
|
|
163
|
+
* @returns Array of validation error messages (empty if valid)
|
|
164
|
+
*/
|
|
165
|
+
export function validateServerConfig(config) {
|
|
166
|
+
const result = ServerConfigSchema.safeParse(config);
|
|
167
|
+
if (result.success) {
|
|
168
|
+
return [];
|
|
169
|
+
}
|
|
170
|
+
return result.error.errors.map((e) => {
|
|
171
|
+
const path = e.path.length > 0 ? `${e.path.join(".")}: ` : "";
|
|
172
|
+
return `${path}${e.message}`;
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Parse assessment options with validation.
|
|
177
|
+
*
|
|
178
|
+
* @param options - Raw options object
|
|
179
|
+
* @returns Validated options
|
|
180
|
+
* @throws ZodError if validation fails
|
|
181
|
+
*/
|
|
182
|
+
export function parseAssessmentOptions(options) {
|
|
183
|
+
return AssessmentOptionsSchema.parse(options);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Safely parse assessment options without throwing.
|
|
187
|
+
*
|
|
188
|
+
* @param options - Raw options object
|
|
189
|
+
* @returns SafeParseResult with success status and data/error
|
|
190
|
+
*/
|
|
191
|
+
export function safeParseAssessmentOptions(options) {
|
|
192
|
+
return AssessmentOptionsSchema.safeParse(options);
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Validate module names from a comma-separated string.
|
|
196
|
+
*
|
|
197
|
+
* @param input - Comma-separated module names
|
|
198
|
+
* @returns Array of validated module names
|
|
199
|
+
* @throws ZodError if any module name is invalid
|
|
200
|
+
*/
|
|
201
|
+
export function parseModuleNames(input) {
|
|
202
|
+
const names = input
|
|
203
|
+
.split(",")
|
|
204
|
+
.map((n) => n.trim())
|
|
205
|
+
.filter(Boolean);
|
|
206
|
+
return z.array(AssessmentModuleNameSchema).parse(names);
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Safely validate module names without throwing.
|
|
210
|
+
*
|
|
211
|
+
* @param input - Comma-separated module names
|
|
212
|
+
* @returns Object with valid module names and any invalid names
|
|
213
|
+
*/
|
|
214
|
+
export function safeParseModuleNames(input) {
|
|
215
|
+
const names = input
|
|
216
|
+
.split(",")
|
|
217
|
+
.map((n) => n.trim())
|
|
218
|
+
.filter(Boolean);
|
|
219
|
+
const valid = [];
|
|
220
|
+
const invalid = [];
|
|
221
|
+
for (const name of names) {
|
|
222
|
+
const result = AssessmentModuleNameSchema.safeParse(name);
|
|
223
|
+
if (result.success) {
|
|
224
|
+
valid.push(result.data);
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
invalid.push(name);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return { valid, invalid };
|
|
231
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zod Error Formatting Utilities
|
|
3
|
+
*
|
|
4
|
+
* CLI-friendly error message formatting for Zod validation errors.
|
|
5
|
+
*
|
|
6
|
+
* @module cli/lib/zodErrorFormatter
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Format a single Zod issue into a readable string.
|
|
10
|
+
*
|
|
11
|
+
* @param issue - Zod validation issue
|
|
12
|
+
* @returns Formatted error message
|
|
13
|
+
*/
|
|
14
|
+
export function formatZodIssue(issue) {
|
|
15
|
+
const path = issue.path.length > 0 ? `${issue.path.join(".")}: ` : "";
|
|
16
|
+
return `${path}${issue.message}`;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Format a ZodError into a readable string.
|
|
20
|
+
*
|
|
21
|
+
* @param error - Zod validation error
|
|
22
|
+
* @returns Formatted error messages joined by newlines
|
|
23
|
+
*/
|
|
24
|
+
export function formatZodError(error) {
|
|
25
|
+
return error.errors.map(formatZodIssue).join("\n");
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Format a ZodError with indentation for CLI output.
|
|
29
|
+
*
|
|
30
|
+
* @param error - Zod validation error
|
|
31
|
+
* @param indent - Indentation string (default: " ")
|
|
32
|
+
* @returns Formatted error messages with indentation
|
|
33
|
+
*/
|
|
34
|
+
export function formatZodErrorIndented(error, indent = " ") {
|
|
35
|
+
return error.errors.map((e) => `${indent}${formatZodIssue(e)}`).join("\n");
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Print a formatted Zod error to stderr with context.
|
|
39
|
+
*
|
|
40
|
+
* @param error - Zod validation error
|
|
41
|
+
* @param context - Context description (e.g., "config file", "CLI arguments")
|
|
42
|
+
*/
|
|
43
|
+
export function printZodErrorForCli(error, context) {
|
|
44
|
+
const prefix = context ? `Error in ${context}:\n` : "Validation error:\n";
|
|
45
|
+
console.error(prefix + formatZodErrorIndented(error));
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Convert ZodError to an array of error strings.
|
|
49
|
+
* Useful for accumulating errors in a validation result.
|
|
50
|
+
*
|
|
51
|
+
* @param error - Zod validation error
|
|
52
|
+
* @returns Array of formatted error strings
|
|
53
|
+
*/
|
|
54
|
+
export function zodErrorToArray(error) {
|
|
55
|
+
return error.errors.map(formatZodIssue);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Format validation errors for JSON output.
|
|
59
|
+
*
|
|
60
|
+
* @param error - Zod validation error
|
|
61
|
+
* @returns Object with structured error information
|
|
62
|
+
*/
|
|
63
|
+
export function formatZodErrorForJson(error) {
|
|
64
|
+
return {
|
|
65
|
+
message: "Validation failed",
|
|
66
|
+
errors: error.errors.map((e) => ({
|
|
67
|
+
path: e.path,
|
|
68
|
+
message: e.message,
|
|
69
|
+
code: e.code,
|
|
70
|
+
})),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Create a user-friendly error message for common validation issues.
|
|
75
|
+
*
|
|
76
|
+
* @param error - Zod validation error
|
|
77
|
+
* @param fieldLabels - Optional map of field names to user-friendly labels
|
|
78
|
+
* @returns User-friendly error message
|
|
79
|
+
*/
|
|
80
|
+
export function formatUserFriendlyError(error, fieldLabels) {
|
|
81
|
+
const messages = error.errors.map((issue) => {
|
|
82
|
+
const fieldPath = issue.path.join(".");
|
|
83
|
+
const label = fieldLabels?.[fieldPath] || fieldPath;
|
|
84
|
+
const prefix = label ? `${label}: ` : "";
|
|
85
|
+
return `${prefix}${issue.message}`;
|
|
86
|
+
});
|
|
87
|
+
if (messages.length === 1) {
|
|
88
|
+
return messages[0];
|
|
89
|
+
}
|
|
90
|
+
return `Multiple validation errors:\n${messages.map((m) => ` - ${m}`).join("\n")}`;
|
|
91
|
+
}
|
package/package.json
CHANGED