11import { z } from "zod" ;
22import { parser } from "zod-opts" ;
33
4- import type { CliTransform , CommandDefinition } from "./commands/types.js" ;
4+ import type { CliTransform } from "./commands/types.js" ;
55import { ValidationError } from "./errors.js" ;
66
77function unwrapSchema ( schema : z . ZodType ) : z . ZodType {
@@ -34,9 +34,9 @@ function coerceForZodOpts(fieldSchema: z.ZodType): z.ZodType {
3434 const element = unwrapSchema ( inner . element as z . ZodType ) ;
3535 if ( element instanceof z . ZodEnum ) {
3636 const coerced = z . array ( z . string ( ) ) ;
37- return fieldSchema instanceof z . ZodOptional
38- ? coerced . optional ( )
39- : coerced ;
37+ if ( fieldSchema instanceof z . ZodOptional ) return coerced . optional ( ) ;
38+ if ( fieldSchema instanceof z . ZodDefault ) return coerced . default ( [ ] ) ;
39+ return coerced ;
4040 }
4141 }
4242 return fieldSchema ;
@@ -86,7 +86,9 @@ function getDefaultValue(
8686
8787function isOptionalField ( schema : z . ZodType ) : boolean {
8888 return (
89- schema instanceof z . ZodOptional || getDefaultValue ( schema ) !== undefined
89+ schema instanceof z . ZodOptional ||
90+ schema instanceof z . ZodNullable ||
91+ getDefaultValue ( schema ) !== undefined
9092 ) ;
9193}
9294
@@ -128,8 +130,14 @@ export interface FieldInfo {
128130 objectKeys ?: string [ ] | undefined ;
129131}
130132
133+ export interface DescribeCommandInput {
134+ schema : z . ZodType ;
135+ positionalArgs ?: string [ ] | undefined ;
136+ cliTransforms ?: Record < string , CliTransform > | undefined ;
137+ }
138+
131139/** Describe all fields of a command — single source of truth for --help and COMMANDS.md. */
132- export function describeCommand ( cmd : CommandDefinition ) : FieldInfo [ ] {
140+ export function describeCommand ( cmd : DescribeCommandInput ) : FieldInfo [ ] {
133141 const shape = getSchemaShape ( cmd . schema ) ;
134142 const positionalFieldSet = new Set ( cmd . positionalArgs ?? [ ] ) ;
135143 const transformFieldSet = new Set ( Object . keys ( cmd . cliTransforms ?? { } ) ) ;
@@ -199,7 +207,6 @@ export function describeCommand(cmd: CommandDefinition): FieldInfo[] {
199207
200208export interface BuiltParser {
201209 parse : ( argv : string [ ] ) => Record < string , unknown > ;
202- showHelp : ( ) => void ;
203210 jsonFallbackFields : string [ ] ;
204211}
205212
@@ -212,12 +219,7 @@ export function buildParser(
212219) : BuiltParser {
213220 const shape = getSchemaShape ( schema ) ;
214221
215- const cmd = {
216- schema,
217- positionalArgs,
218- cliTransforms,
219- } as CommandDefinition ;
220- const fields = describeCommand ( cmd ) ;
222+ const fields = describeCommand ( { schema, positionalArgs, cliTransforms } ) ;
221223
222224 const options : Record < string , { type : z . ZodType } > = { } ;
223225 const jsonFallbackFields : string [ ] = [ ] ;
@@ -249,15 +251,6 @@ export function buildParser(
249251 }
250252 }
251253
252- options [ "json" ] = {
253- type : z
254- . string ( )
255- . optional ( )
256- . describe (
257- "Raw JSON input — bypasses all other flags (use '-' for stdin)"
258- ) ,
259- } ;
260-
261254 let p = parser ( ) . name ( commandName ) . options ( options ) ;
262255
263256 const positionalFields = fields . filter ( ( f ) => f . isPositional ) ;
@@ -272,10 +265,27 @@ export function buildParser(
272265 p = p . args ( args ) ;
273266 }
274267
268+ let parseResult : Record < string , unknown > | undefined ;
269+
270+ p . _internalHandler ( ( r ) => {
271+ switch ( r . type ) {
272+ case "match" :
273+ parseResult = r . parsed as Record < string , unknown > ;
274+ break ;
275+ case "error" :
276+ throw new ValidationError ( r . error . message ) ;
277+ case "help" :
278+ case "version" :
279+ break ;
280+ }
281+ } ) ;
282+
275283 return {
276- parse : ( argv : string [ ] ) => p . parse ( argv ) as Record < string , unknown > ,
277- showHelp : ( ) => {
278- p . parse ( [ "--help" ] ) ;
284+ parse : ( argv : string [ ] ) : Record < string , unknown > => {
285+ parseResult = undefined ;
286+ p . parse ( argv ) ;
287+ if ( ! parseResult ) throw new ValidationError ( "Failed to parse arguments" ) ;
288+ return parseResult ;
279289 } ,
280290 jsonFallbackFields,
281291 } ;
@@ -337,9 +347,8 @@ export async function parseCommand(
337347 const built = buildParser ( schema , commandName , positionalArgs , cliTransforms ) ;
338348 const result = built . parse ( argv ) ;
339349
340- const { json : _json , ...rawParams } = result ;
341350 const params : Record < string , unknown > = { } ;
342- const mutableRawParams = { ...rawParams } ;
351+ const mutableRawParams = { ...result } ;
343352
344353 for ( const [ fieldName , transform ] of Object . entries ( cliTransforms ?? { } ) ) {
345354 const values : Record < string , unknown > = { } ;
0 commit comments