@@ -348,6 +348,18 @@ function isZodArrayType(zodType: any): boolean {
348348 return innerType instanceof z . ZodArray || ( innerType . _def && innerType . _def . typeName === 'ZodArray' ) ;
349349}
350350
351+ /**
352+ * Creates an error for unknown options with proper flag prefix and display name.
353+ *
354+ * @param optionName - The unknown option name
355+ * @returns ZliError with formatted error message
356+ */
357+ function createUnknownOptionError ( optionName : string ) : ZliError {
358+ const flagPrefix = optionName . length === 1 ? '-' : '--' ;
359+ const displayName = optionName . length === 1 ? optionName : camelToKebab ( optionName ) ;
360+ return new ZliError ( `Unknown option: \x1b[36m${ flagPrefix } ${ displayName } \x1b[0m` ) ;
361+ }
362+
351363/**
352364 * Extracts the default value from a Zod type, handling nested optional and default wrappers.
353365 *
@@ -390,11 +402,12 @@ function extractDefaultValue(zodType: any): string | undefined {
390402 * Validates and transforms command options using Zod schema validation.
391403 * Processes aliases and kebab-case conversion before validation.
392404 * Ensures that single values for array fields are converted to arrays.
405+ * Throws an error if any unknown options are provided.
393406 *
394407 * @param flags - Raw parsed flags from command line
395408 * @param optionsDef - Optional options definition with schema and aliases
396409 * @returns Validated options object matching the schema
397- * @throws Error if validation fails
410+ * @throws Error if validation fails or unknown options are provided
398411 *
399412 * @example
400413 * validateOptions({ 'android-max': '10' }, { schema: z.object({ androidMax: z.string() }) })
@@ -409,15 +422,52 @@ function validateOptions<T extends z.ZodObject<any> = z.ZodObject<any>>(
409422 optionsDef ?: OptionsDefinition < T > ,
410423) : any {
411424 if ( ! optionsDef ) {
425+ // Check for unknown options when no schema is defined
426+ const { _, ...options } = flags ;
427+ const unknownOptions = Object . keys ( options ) ;
428+ if ( unknownOptions . length > 0 ) {
429+ // Find the first unknown option that looks like a flag
430+ const firstUnknown = unknownOptions . find ( ( key ) => key !== 'help' && key !== 'version' ) ;
431+ if ( firstUnknown ) {
432+ throw createUnknownOptionError ( firstUnknown ) ;
433+ }
434+ }
412435 return { } ;
413436 }
414437
438+ // Get valid option names from schema
439+ const schemaKeys = Object . keys ( optionsDef . schema . shape ) ;
440+ const validNames = new Set < string > ( ) ;
441+
442+ // Add camelCase names
443+ schemaKeys . forEach ( ( key ) => validNames . add ( key ) ) ;
444+
445+ // Add kebab-case versions
446+ schemaKeys . forEach ( ( key ) => validNames . add ( camelToKebab ( key ) ) ) ;
447+
448+ // Add aliases
449+ if ( optionsDef . aliases ) {
450+ Object . keys ( optionsDef . aliases ) . forEach ( ( alias ) => validNames . add ( alias ) ) ;
451+ }
452+
453+ // Add standard help and version flags
454+ validNames . add ( 'help' ) ;
455+ validNames . add ( 'version' ) ;
456+
457+ // Check for unknown options before processing
458+ const { _, ...options } = flags ;
459+ for ( const optionName of Object . keys ( options ) ) {
460+ if ( ! validNames . has ( optionName ) ) {
461+ throw createUnknownOptionError ( optionName ) ;
462+ }
463+ }
464+
415465 const resolvedAliases = resolveAliases ( flags , optionsDef . aliases ) ;
416466 const resolvedKebab = resolveKebabCase ( resolvedAliases , optionsDef . schema ) ;
417- const { _, ...options } = resolvedKebab ;
467+ const { _ : resolvedUnderscore , ...resolvedOptions } = resolvedKebab ;
418468
419469 // Normalize single values to arrays for fields that expect arrays
420- const normalizedOptions = normalizeArrayFields ( options , optionsDef . schema ) ;
470+ const normalizedOptions = normalizeArrayFields ( resolvedOptions , optionsDef . schema ) ;
421471
422472 return optionsDef . schema . parse ( normalizedOptions ) ;
423473}
0 commit comments